From 12262cec473328e290f5d6e2e496861c8af5b654 Mon Sep 17 00:00:00 2001 From: Rhys Mainwaring Date: Thu, 5 Aug 2021 14:38:23 +0100 Subject: [PATCH] [Metal] Add Metal support to Minimal Scene - Set ogre2 render engine as default and ensure the Qt surface format supports OpenGL 4.1 - Default ogre render engine to version to ogre2 in CMakeLists.txt, scene3d.config and Scene3D. - Set default QSurfaceFormat to OpenGL 4.1 in applications - cherry-pick 066d276f3756ca83d7c50faf497a2dd2c4f3a1d2 - Experimental support for Metal in Scene3D and MinimalScene plugins (OpenGL is disabled) - Modify examples/standalone/window to use Metal backend in the Qt scene graph and force render loop to be single threaded - Modify ign.cc commandlets to use Metal backend in the Qt scene graph and force render loop to be single threaded - Use CFBridgingRetain / CFBridgingRelease to manage access to Metal texture objects. - Force render engine to initialise on the main thread. - Fix build after interface changes - Fix build after changes in https://github.com/ignitionrobotics/ign-rendering/pull/477 - Reverse changes to isolate issues in transform control - Add cross-platform support to MinimalScene TextureNode - Introduce abstract delegate class for the TextureNode - Add implementations for OpenGL and Metal - Add cross-platform support to MinimalScene RenderThread - Introduce abstract delegate class for the RenderThread - Add implementations for OpenGL and Metal - Update CMakeLists.txt for cross-platform - Only include objective-c and Apple frameworks for macOS - Add render interface for a camera texture - Change name of render interface files - Refactor file names to use `Rhi` suffix - Refactor names of render interface classes - Replace Delegate with Rhi (render hardware interface - from Qt) - Add render hardware interface for the ignition renderer - Update MinimalScene to use a render hardware interface to retrieve the camera texture - Convert MinimalScene back to c++ - All Metal specific code moved out of MinimalScene to render interface classes - Rename MinimalScene and compile as c++ - Allow render system to be switched between Metal and OpenGL - Add method SetRenderSystem - Update classes in MinimalScene to allow the render system to be set in the plugin XML - Move all scene graph backend config to Application - Set textureDirty flag true on initialisation - Fix an issue where the initial texture is not sized correctly on initialisation leading to aliasing artefacts - Fix after rebasing onto ign-gui6 [QML] update function call syntax in Connections blocks - Change syntax to address warning: QML Connections: Implicitly defined onFoo properties in Connections are deprecated - Qt 5.15, see: https://doc.qt.io/qt-5/qml-qtqml-connections.html - Add missing properties for tooltopDelay - cherry-pick ac51e07eb72a29a81da6916a2fee7d76a037b9d7 Signed-off-by: Rhys Mainwaring --- CMakeLists.txt | 8 +- examples/standalone/window/window.cc | 42 +- include/ignition/gui/qml/PluginMenu.qml | 5 +- src/Application.cc | 29 + src/plugins/grid_3d/Grid3D_TEST.cc | 4 +- src/plugins/minimal_scene/CMakeLists.txt | 33 +- src/plugins/minimal_scene/MinimalScene.cc | 311 ++- src/plugins/minimal_scene/MinimalScene.hh | 66 +- src/plugins/minimal_scene/MinimalSceneRhi.cc | 55 + src/plugins/minimal_scene/MinimalSceneRhi.hh | 73 + .../minimal_scene/MinimalSceneRhiMetal.hh | 85 + .../minimal_scene/MinimalSceneRhiMetal.mm | 223 ++ .../minimal_scene/MinimalSceneRhiOpenGL.cc | 282 +++ .../minimal_scene/MinimalSceneRhiOpenGL.hh | 89 + src/plugins/scene3d/CMakeLists.txt | 27 +- src/plugins/scene3d/Scene3D.cc | 13 +- src/plugins/scene3d/Scene3D.hh | 38 +- src/plugins/scene3d/Scene3D.mm | 1998 +++++++++++++++++ test/integration/scene3d.cc | 4 +- 19 files changed, 3239 insertions(+), 146 deletions(-) create mode 100644 src/plugins/minimal_scene/MinimalSceneRhi.cc create mode 100644 src/plugins/minimal_scene/MinimalSceneRhi.hh create mode 100644 src/plugins/minimal_scene/MinimalSceneRhiMetal.hh create mode 100644 src/plugins/minimal_scene/MinimalSceneRhiMetal.mm create mode 100644 src/plugins/minimal_scene/MinimalSceneRhiOpenGL.cc create mode 100644 src/plugins/minimal_scene/MinimalSceneRhiOpenGL.hh create mode 100644 src/plugins/scene3d/Scene3D.mm diff --git a/CMakeLists.txt b/CMakeLists.txt index fc5c94242..5c1447adc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,12 +39,12 @@ ign_find_package(TINYXML2 REQUIRED PRIVATE PRETTY tinyxml2) #-------------------------------------- # Find ignition-math -ign_find_package(ignition-math6 REQUIRED VERSION 6.5) +ign_find_package(ignition-math6 REQUIRED VERSION 6.9) set(IGN_MATH_VER ${ignition-math6_VERSION_MAJOR}) #-------------------------------------- # Find ignition-common -ign_find_package(ignition-common4 REQUIRED COMPONENTS profiler VERSION 4.1) +ign_find_package(ignition-common4 REQUIRED COMPONENTS profiler VERSION 4.4) set(IGN_COMMON_VER ${ignition-common4_VERSION_MAJOR}) #-------------------------------------- @@ -59,8 +59,8 @@ set(IGN_TRANSPORT_VER ${ignition-transport11_VERSION_MAJOR}) #-------------------------------------- # Find ignition-rendering -ign_find_package(ignition-rendering6 REQUIRED COMPONENTS ogre) -set(IGN_RENDERING_VER ${ignition-rendering6_VERSION_MAJOR}) +ign_find_package(ignition-rendering7 REQUIRED COMPONENTS ogre2) +set(IGN_RENDERING_VER ${ignition-rendering7_VERSION_MAJOR}) #-------------------------------------- # Find ignition-msgs diff --git a/examples/standalone/window/window.cc b/examples/standalone/window/window.cc index 8986216a1..779b250c7 100644 --- a/examples/standalone/window/window.cc +++ b/examples/standalone/window/window.cc @@ -16,6 +16,7 @@ */ #include +#include #ifndef Q_MOC_RUN #include @@ -23,19 +24,58 @@ #include #endif +#include + ////////////////////////////////////////////////// int main(int _argc, char **_argv) { std::cout << "Hello, GUI!" << std::endl; + // must be called before the widget or its parent window gets shown +// QSurfaceFormat format(QSurfaceFormat::DeprecatedFunctions); +// format.setDepthBufferSize(24); +// format.setStencilBufferSize(8); +// format.setVersion(4, 1); +// format.setProfile(QSurfaceFormat::CoreProfile); +// format.setRenderableType(QSurfaceFormat::OpenGL); +// QSurfaceFormat::setDefaultFormat(format); + + // TODO: OpenGL/Metal + // Use single-threaded scene graph rendering + qputenv("QSG_RENDER_LOOP", "basic"); + + // TODO: OpenGL/Metal + // Use Metal backend + QQuickWindow::setSceneGraphBackend(QSGRendererInterface::MetalRhi); + // Increase verboosity so we see all messages ignition::common::Console::SetVerbosity(4); // Create app ignition::gui::Application app(_argc, _argv); + // Create plugin XML + std::string _filename(""); + std::ostringstream ss; + ss << "" + << " " + << " View 1" + << " docked" + << " " + << " ogre2" + << " scene" + << " " + << " 1 1 1" + << " 0.8 0.8 0.8" + << " -6 0 6 0 0.5 0" + << "\n"; + + tinyxml2::XMLDocument pluginDoc; + pluginDoc.Parse(ss.str().c_str()); + tinyxml2::XMLElement *pluginElem{pluginDoc.FirstChildElement("plugin")}; + // Load plugins / config - if (!app.LoadPlugin("Publisher")) + if (!app.LoadPlugin("MinimalScene", pluginElem)) { return 1; } diff --git a/include/ignition/gui/qml/PluginMenu.qml b/include/ignition/gui/qml/PluginMenu.qml index 68de63a7c..7e5153d60 100644 --- a/include/ignition/gui/qml/PluginMenu.qml +++ b/include/ignition/gui/qml/PluginMenu.qml @@ -48,7 +48,10 @@ Popup { id: searchSortBar color: searchColor height: 50 - width: parent.width + // incorrect: https://stackoverflow.com/questions/63767669/parent-is-null-in-listview-delegate-after-upgrade-to-qt-5-15 + // width: parent.width + width: pluginMenuListView.width + RowLayout { id: rowLayout anchors.fill: parent diff --git a/src/Application.cc b/src/Application.cc index 57104b09b..d3b23ef55 100644 --- a/src/Application.cc +++ b/src/Application.cc @@ -86,6 +86,35 @@ Application::Application(int &_argc, char **_argv, const WindowType _type) { igndbg << "Initializing application." << std::endl; + // TODO(srmainwaring): OpenGL/Metal + std::string renderSystem = "metal"; + if (renderSystem.compare("opengl") == 0) + { + // Must be called before the widget or its parent window gets shown + QSurfaceFormat format(QSurfaceFormat::DeprecatedFunctions); + format.setDepthBufferSize(24); + format.setStencilBufferSize(8); + format.setVersion(4, 1); + format.setProfile(QSurfaceFormat::CoreProfile); + format.setRenderableType(QSurfaceFormat::OpenGL); + QSurfaceFormat::setDefaultFormat(format); + } +#if __APPLE__ + else if (renderSystem.compare("metal") == 0) + { + igndbg << "Qt using Metal rendering interface" << std::endl; + qputenv("QSG_RENDER_LOOP", "basic"); + QQuickWindow::setSceneGraphBackend(QSGRendererInterface::MetalRhi); + } +#endif + else + { + ignerr << "Invalid render system: " << renderSystem << std::endl; + } + + // Increase verbosity so we see all messages + ignition::common::Console::SetVerbosity(4); + // Configure console common::Console::SetPrefix("[GUI] "); diff --git a/src/plugins/grid_3d/Grid3D_TEST.cc b/src/plugins/grid_3d/Grid3D_TEST.cc index 818bb35e3..ae2995324 100644 --- a/src/plugins/grid_3d/Grid3D_TEST.cc +++ b/src/plugins/grid_3d/Grid3D_TEST.cc @@ -45,7 +45,7 @@ TEST(Grid3DTest, WithoutScene) EXPECT_TRUE(createMainWindow()); // Check there is no scene - auto engine = rendering::engine("ogre"); + auto engine = rendering::engine("ogre2"); ASSERT_NE(nullptr, engine); EXPECT_EQ(0u, engine->SceneCount()); @@ -65,7 +65,7 @@ TEST(Grid3DTest, CRUD) // Load plugin const char *pluginStr = "" - "ogre" + "ogre2" "scene" "" "5" diff --git a/src/plugins/minimal_scene/CMakeLists.txt b/src/plugins/minimal_scene/CMakeLists.txt index 7f98b9908..a5ac7cd4b 100644 --- a/src/plugins/minimal_scene/CMakeLists.txt +++ b/src/plugins/minimal_scene/CMakeLists.txt @@ -1,9 +1,40 @@ +set(SOURCES + MinimalScene.cc + MinimalSceneRhi.cc + MinimalSceneRhiOpenGL.cc +) + +set(PROJECT_LINK_LIBS "") + +# Objective-C sources for macOS +if (APPLE) + set(SOURCES + ${SOURCES} + MinimalSceneRhiMetal.mm + ) + + set(PROJECT_LINK_LIBS + "-framework AppKit" + "-framework Metal" + ) +endif() + ign_gui_add_plugin(MinimalScene SOURCES - MinimalScene.cc + ${SOURCES} QT_HEADERS MinimalScene.hh PUBLIC_LINK_LIBS ignition-rendering${IGN_RENDERING_VER}::ignition-rendering${IGN_RENDERING_VER} + ${PROJECT_LINK_LIBS} ) +# Enable ARC on selected source files +if (APPLE) + set_source_files_properties( + MinimalSceneRhiMetal.mm + PROPERTIES + COMPILE_FLAGS + "-fobjc-arc" + ) +endif() diff --git a/src/plugins/minimal_scene/MinimalScene.cc b/src/plugins/minimal_scene/MinimalScene.cc index 5b167094f..fcafcf722 100644 --- a/src/plugins/minimal_scene/MinimalScene.cc +++ b/src/plugins/minimal_scene/MinimalScene.cc @@ -16,6 +16,9 @@ */ #include "MinimalScene.hh" +#include "MinimalSceneRhi.hh" +#include "MinimalSceneRhiMetal.hh" +#include "MinimalSceneRhiOpenGL.hh" #include #include @@ -92,6 +95,12 @@ class ignition::gui::plugins::IgnRenderer::Implementation /// \brief View control focus target public: math::Vector3d target; + + /// \brief Render system parameters + public: std::map rhi_params; + + /// \brief Render hardware interface for the texture + public: std::unique_ptr rhi; }; /// \brief Qt and Ogre rendering is happening in different threads @@ -183,6 +192,12 @@ class ignition::gui::plugins::RenderWindowItem::Implementation /// \brief Keep latest mouse event public: common::MouseEvent mouseEvent; + /// \brief True if initialized + public: bool initialized = false; + + /// \brief Render system + public: std::string renderSystem = "opengl"; + /// \brief Render thread public: RenderThread *renderThread = nullptr; @@ -239,7 +254,6 @@ void RenderSync::WaitForWorkerThread() // Worker thread asked us to wait! this->renderStallState = RenderStallState::WorkerCanProceed; - lock.unlock(); // Wake up worker thread this->cv.notify_one(); @@ -270,6 +284,8 @@ void RenderSync::Shutdown() IgnRenderer::IgnRenderer() : dataPtr(utils::MakeUniqueImpl()) { + // Set default render system to OpenGL + this->SetRenderSystem("opengl"); } ///////////////////////////////////////////////// @@ -300,7 +316,8 @@ void IgnRenderer::Render(RenderSync *_renderSync) // _renderSync->ReleaseQtThreadFromBlock(lock); } - this->textureId = this->dataPtr->camera->RenderTextureGLId(); + // Update the render interface (texture) + this->dataPtr->rhi->Update(this->dataPtr->camera); // view control this->HandleMouseEvent(); @@ -503,16 +520,21 @@ void IgnRenderer::BroadcastKeyPress() ///////////////////////////////////////////////// void IgnRenderer::Initialize() +{ + // no-op +} + +///////////////////////////////////////////////// +void IgnRenderer::InitialiseOnMainThread() { if (this->initialized) return; - std::map params; - params["useCurrentGLContext"] = "1"; - params["winID"] = std::to_string( + this->dataPtr->rhi_params["winID"] = std::to_string( ignition::gui::App()->findChild()-> QuickWindow()->winId()); - auto engine = rendering::engine(this->engineName, params); + auto engine = rendering::engine( + this->engineName, this->dataPtr->rhi_params); if (!engine) { ignerr << "Engine [" << this->engineName << "] is not supported" @@ -551,7 +573,9 @@ void IgnRenderer::Initialize() // setting the size and calling PreRender should cause the render texture to // be rebuilt this->dataPtr->camera->PreRender(); - this->textureId = this->dataPtr->camera->RenderTextureGLId(); + + // Update the render interface (texture) + this->dataPtr->rhi->Update(this->dataPtr->camera); // Ray Query this->dataPtr->rayQuery = this->dataPtr->camera->Scene()->CreateRayQuery(); @@ -559,6 +583,28 @@ void IgnRenderer::Initialize() this->initialized = true; } +///////////////////////////////////////////////// +void IgnRenderer::SetRenderSystem(const std::string& _renderSystem) +{ + // Create render interface and reset params + this->dataPtr->rhi_params.clear(); + + if (_renderSystem.compare("opengl") == 0) + { + qDebug().nospace() << "Creating ign-renderering interface for OpenGL"; + this->dataPtr->rhi_params["useCurrentGLContext"] = "1"; + this->dataPtr->rhi = std::make_unique(); + } +#ifdef __APPLE__ + else if (_renderSystem.compare("metal") == 0) + { + qDebug().nospace() << "Creating ign-renderering interface for Metal"; + this->dataPtr->rhi_params["metal"] = "1"; + this->dataPtr->rhi = std::make_unique(); + } +#endif +} + ///////////////////////////////////////////////// void IgnRenderer::Destroy() { @@ -635,9 +681,18 @@ math::Vector3d IgnRenderer::ScreenToScene( this->dataPtr->rayQuery->Direction() * 10; } +///////////////////////////////////////////////// +void IgnRenderer::TextureId(void* _texturePtr) +{ + this->dataPtr->rhi->TextureId(_texturePtr); +} + ///////////////////////////////////////////////// RenderThread::RenderThread() { + // Set default render system to OpenGL + this->SetRenderSystem("opengl"); + RenderWindowItem::Implementation::threads << this; qRegisterMetaType("RenderSync*"); } @@ -645,38 +700,17 @@ RenderThread::RenderThread() ///////////////////////////////////////////////// void RenderThread::RenderNext(RenderSync *_renderSync) { - this->context->makeCurrent(this->surface); - - if (!this->ignRenderer.initialized) - { - // Initialize renderer - this->ignRenderer.Initialize(); - } - - // check if engine has been successfully initialized - if (!this->ignRenderer.initialized) - { - ignerr << "Unable to initialize renderer" << std::endl; - return; - } - - this->ignRenderer.Render(_renderSync); - - emit TextureReady(this->ignRenderer.textureId, this->ignRenderer.textureSize); + this->rhi->RenderNext(_renderSync); + emit this->TextureReady( + this->rhi->TexturePtr(), + this->rhi->TextureSize()); } ///////////////////////////////////////////////// void RenderThread::ShutDown() { - this->context->makeCurrent(this->surface); - - this->ignRenderer.Destroy(); - - this->context->doneCurrent(); - delete this->context; - - // schedule this to be deleted only after we're done cleaning up - this->surface->deleteLater(); + // The render interface calls Destroy on IgnRendering + this->rhi->ShutDown(); // Stop event processing, move the thread to GUI and make sure it is deleted. this->exit(); @@ -701,34 +735,87 @@ void RenderThread::SizeChanged() } ///////////////////////////////////////////////// -TextureNode::TextureNode(QQuickWindow *_window, RenderSync &_renderSync) - : renderSync(_renderSync), window(_window) +QOffscreenSurface *RenderThread::Surface() const { - // Our texture node must have a texture, so use the default 0 texture. -#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) - this->texture = this->window->createTextureFromId(0, QSize(1, 1)); -#else - void * nativeLayout; - this->texture = this->window->createTextureFromNativeObject( - QQuickWindow::NativeObjectTexture, &nativeLayout, 0, QSize(1, 1), - QQuickWindow::TextureIsOpaque); + return this->rhi->Surface(); +} + +///////////////////////////////////////////////// +void RenderThread::SetSurface(QOffscreenSurface *_surface) +{ + this->rhi->SetSurface(_surface); +} + +///////////////////////////////////////////////// +QOpenGLContext *RenderThread::Context() const +{ + return this->rhi->Context(); +} + +///////////////////////////////////////////////// +void RenderThread::SetContext(QOpenGLContext *_context) +{ + this->rhi->SetContext(_context); +} + +///////////////////////////////////////////////// +void RenderThread::SetRenderSystem(const std::string &_renderSystem) +{ + // Set the render system for the IgnRenderer + this->ignRenderer.SetRenderSystem(_renderSystem); + + // Create the render interface + if (_renderSystem.compare("opengl") == 0) + { + qDebug().nospace() << "Creating render thread interface for OpenGL"; + this->rhi = std::make_unique(&this->ignRenderer); + } +#ifdef __APPLE__ + else if (_renderSystem.compare("metal") == 0) + { + qDebug().nospace() << "Creating render thread interface for Metal"; + this->rhi = std::make_unique(&this->ignRenderer); + } #endif - this->setTexture(this->texture); } ///////////////////////////////////////////////// -TextureNode::~TextureNode() +void RenderThread::InitialiseOnMainThread() { - delete this->texture; + this->rhi->InitialiseOnMainThread(); } ///////////////////////////////////////////////// -void TextureNode::NewTexture(uint _id, const QSize &_size) +TextureNode::TextureNode( + QQuickWindow *_window, + RenderSync &_renderSync, + const std::string &_renderSystem) + : renderSync(_renderSync) + , window(_window) +{ + if (_renderSystem.compare("opengl") == 0) + { + qDebug().nospace() << "Creating texture node render interface for OpenGL"; + this->rhi = std::make_unique(_window); + } +#ifdef __APPLE__ + else if (_renderSystem.compare("metal") == 0) + { + qDebug().nospace() << "Creating texture node render interface for Metal"; + this->rhi = std::make_unique(_window); + } +#endif + + this->setTexture(this->rhi->Texture()); +} + +///////////////////////////////////////////////// +TextureNode::~TextureNode() = default; + +///////////////////////////////////////////////// +void TextureNode::NewTexture(void* _texturePtr, const QSize &_size) { - this->mutex.lock(); - this->id = _id; - this->size = _size; - this->mutex.unlock(); + this->rhi->NewTexture(_texturePtr, _size); // We cannot call QQuickWindow::update directly here, as this is only allowed // from the rendering thread or GUI thread. @@ -738,34 +825,11 @@ void TextureNode::NewTexture(uint _id, const QSize &_size) ///////////////////////////////////////////////// void TextureNode::PrepareNode() { - this->mutex.lock(); - uint newId = this->id; - QSize sz = this->size; - this->id = 0; - this->mutex.unlock(); - if (newId) - { - delete this->texture; - // note: include QQuickWindow::TextureHasAlphaChannel if the rendered - // content has alpha. -#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) - this->texture = this->window->createTextureFromId( - newId, sz, QQuickWindow::TextureIsOpaque); -#else - // TODO(anyone) Use createTextureFromNativeObject - // https://github.com/ignitionrobotics/ign-gui/issues/113 -#ifndef _WIN32 -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wdeprecated-declarations" -#endif - this->texture = this->window->createTextureFromId( - newId, sz, QQuickWindow::TextureIsOpaque); -#ifndef _WIN32 -# pragma GCC diagnostic pop -#endif + this->rhi->PrepareNode(); -#endif - this->setTexture(this->texture); + if (this->rhi->HasNewTexture()) + { + this->setTexture(this->rhi->Texture()); this->markDirty(DirtyMaterial); @@ -825,14 +889,27 @@ RenderWindowItem::~RenderWindowItem() ///////////////////////////////////////////////// void RenderWindowItem::Ready() { - this->dataPtr->renderThread->surface = new QOffscreenSurface(); - this->dataPtr->renderThread->surface->setFormat( - this->dataPtr->renderThread->context->format()); - this->dataPtr->renderThread->surface->create(); + if (this->dataPtr->renderSystem.compare("opengl") == 0) + { + // Run on the main (GUI) thread + this->dataPtr->renderThread->SetSurface(new QOffscreenSurface()); + this->dataPtr->renderThread->Surface()->setFormat( + this->dataPtr->renderThread->Context()->format()); + this->dataPtr->renderThread->Surface()->create(); + } + + // Carry out any initialisation before moving to thread + this->dataPtr->renderThread->InitialiseOnMainThread(); this->dataPtr->renderThread->ignRenderer.textureSize = QSize(std::max({this->width(), 1.0}), std::max({this->height(), 1.0})); + // Move to Render thread + if (this->dataPtr->renderSystem.compare("opengl") == 0) + { + this->dataPtr->renderThread->Context()->moveToThread( + this->dataPtr->renderThread); + } this->dataPtr->renderThread->moveToThread(this->dataPtr->renderThread); this->connect(this, &QQuickItem::widthChanged, @@ -840,6 +917,7 @@ void RenderWindowItem::Ready() this->connect(this, &QQuickItem::heightChanged, this->dataPtr->renderThread, &RenderThread::SizeChanged); + // Running on Render thread this->dataPtr->renderThread->start(); this->update(); } @@ -850,30 +928,51 @@ QSGNode *RenderWindowItem::updatePaintNode(QSGNode *_node, { TextureNode *node = static_cast(_node); - if (!this->dataPtr->renderThread->context) + if (!this->dataPtr->initialized) { - QOpenGLContext *current = this->window()->openglContext(); - // Some GL implementations require that the currently bound context is - // made non-current before we set up sharing, so we doneCurrent here - // and makeCurrent down below while setting up our own context. - current->doneCurrent(); - - this->dataPtr->renderThread->context = new QOpenGLContext(); - this->dataPtr->renderThread->context->setFormat(current->format()); - this->dataPtr->renderThread->context->setShareContext(current); - this->dataPtr->renderThread->context->create(); - this->dataPtr->renderThread->context->moveToThread( - this->dataPtr->renderThread); + this->dataPtr->initialized = true; - current->makeCurrent(this->window()); + // Set the render thread's render system + this->dataPtr->renderThread->SetRenderSystem( + this->dataPtr->renderSystem); - QMetaObject::invokeMethod(this, "Ready"); + if (this->dataPtr->renderSystem.compare("opengl") == 0) + { + QOpenGLContext *current = this->window()->openglContext(); + // Some GL implementations require that the currently bound context is + // made non-current before we set up sharing, so we doneCurrent here + // and makeCurrent down below while setting up our own context. + current->doneCurrent(); + + this->dataPtr->renderThread->SetContext(new QOpenGLContext()); + this->dataPtr->renderThread->Context()->setFormat(current->format()); + this->dataPtr->renderThread->Context()->setShareContext(current); + this->dataPtr->renderThread->Context()->create(); + + // Initialisation must be on main thread + // QMetaObject::invokeMethod(this, "Ready"); + this->Ready(); + + current->makeCurrent(this->window()); + + } + else if (this->dataPtr->renderSystem.compare("metal") == 0) + { + // Initialisation must be on main thread + // QMetaObject::invokeMethod(this, "Ready"); + this->Ready(); + } + else + { + // invalid render system + } return nullptr; } if (!node) { - node = new TextureNode(this->window(), this->dataPtr->renderSync); + node = new TextureNode(this->window(), this->dataPtr->renderSync, + this->dataPtr->renderSystem); // Set up connections to get the production of render texture in sync with // vsync on the rendering thread. @@ -988,6 +1087,13 @@ void RenderWindowItem::SetSkyEnabled(const bool &_sky) this->dataPtr->renderThread->ignRenderer.skyEnable = _sky; } +///////////////////////////////////////////////// +void RenderWindowItem::SetRenderSystem(const std::string &_renderSystem) +{ + this->dataPtr->renderSystem = _renderSystem; + this->dataPtr->renderThread->SetRenderSystem(_renderSystem); +} + ///////////////////////////////////////////////// MinimalScene::MinimalScene() : Plugin(), dataPtr(utils::MakeUniqueImpl()) @@ -1133,6 +1239,13 @@ void MinimalScene::LoadConfig(const tinyxml2::XMLElement *_pluginElem) if (!elem->NoChildren()) ignwarn << "Child elements of are not supported yet" << std::endl; } + + elem = _pluginElem->FirstChildElement("render_system"); + if (nullptr != elem && nullptr != elem->GetText()) + { + std::string renderSystem = elem->GetText(); + renderWindow->SetRenderSystem(renderSystem); + } } renderWindow->SetEngineName(cmdRenderEngine); diff --git a/src/plugins/minimal_scene/MinimalScene.hh b/src/plugins/minimal_scene/MinimalScene.hh index 49b72198b..6c9460003 100644 --- a/src/plugins/minimal_scene/MinimalScene.hh +++ b/src/plugins/minimal_scene/MinimalScene.hh @@ -30,6 +30,8 @@ #include "ignition/gui/Plugin.hh" +#include "MinimalSceneRhi.hh" + namespace ignition { namespace gui @@ -108,6 +110,14 @@ namespace plugins /// \brief Initialize the render engine public: void Initialize(); + /// \brief Initialise the render engine and scene. + /// Must be called on the main thread. + public: void InitialiseOnMainThread(); + + /// \brief Set the render system + /// \param[in] _renderSystem The render system name. + public: void SetRenderSystem(const std::string& _renderSystem); + /// \brief Destroy camera associated with this renderer public: void Destroy(); @@ -173,10 +183,11 @@ namespace plugins /// Values is constantly constantly cycled/swapped/changed /// from a worker thread /// Don't read this directly - public: GLuint textureId; + // public: GLuint textureId; + public: void TextureId(void* _texturePtr /*[out]*/); /// \brief Render engine to use - public: std::string engineName = "ogre"; + public: std::string engineName = "ogre2"; /// \brief Unique scene name public: std::string sceneName = "scene"; @@ -203,7 +214,7 @@ namespace plugins public: QSize textureSize = QSize(1024, 1024); /// \brief Flag to indicate texture size has changed. - public: bool textureDirty = false; + public: bool textureDirty = true; /// \brief Scene service. If not empty, a request will be made to get the /// scene information using this service and the renderer will populate the @@ -253,16 +264,36 @@ namespace plugins /// to be displayed /// \param[in] _id GLuid of the opengl texture /// \param[in] _size Size of the texture - signals: void TextureReady(uint _id, const QSize &_size); + signals: void TextureReady(void* _texturePtr /*[in]*/, const QSize &_size); /// \brief Offscreen surface to render to - public: QOffscreenSurface *surface = nullptr; + public: QOffscreenSurface *Surface() const; + + /// \brief Set the offscreen surface to render to + // + /// \param[in] _surface Off-screen surface format + public: void SetSurface(QOffscreenSurface *_surface); /// \brief OpenGL context to be passed to the render engine - public: QOpenGLContext *context = nullptr; + public: QOpenGLContext *Context() const; + + /// \brief Set the OpenGL context to be passed to the render engine + // + /// \param[in] _surface OpenGL context + public: void SetContext(QOpenGLContext *_context); + + /// \brief Set the render system + /// \param[in] _renderSystem The render system name. + public: void SetRenderSystem(const std::string& _renderSystem); + + /// \brief Carry out initialisation that must be run on the main thread + public: void InitialiseOnMainThread(); /// \brief Ign-rendering renderer public: IgnRenderer ignRenderer; + + /// \brief Pointer to render interface to handle OpenGL/Metal compatibility + private: std::unique_ptr rhi; }; /// \brief A QQUickItem that manages the render window @@ -342,6 +373,10 @@ namespace plugins /// \param[in] _sky True to enable the sky, false otherwise. public: void SetSkyEnabled(const bool &_sky); + /// \brief Set the render system + /// \param[in] _renderSystem The render system name. + public: void SetRenderSystem(const std::string& _renderSystem); + /// \brief Slot called when thread is ready to be started public Q_SLOTS: void Ready(); @@ -395,16 +430,18 @@ namespace plugins /// \param[in] _renderSync RenderSync to safely /// synchronize Qt (this) and worker thread public: explicit TextureNode(QQuickWindow *_window, - RenderSync &_renderSync); + RenderSync &_renderSync, + const std::string &_renderSystem); /// \brief Destructor public: ~TextureNode() override; /// \brief This function gets called on the FBO rendering thread and will /// store the texture id and size and schedule an update on the window. - /// \param[in] _id OpenGL render texture Id + /// \param[in] _texturePtr Pointer to a texture Id /// \param[in] _size Texture size - public slots: void NewTexture(uint _id, const QSize &_size); + // public slots: void NewTexture(uint _id, const QSize &_size); + public slots: void NewTexture(void* _texturePtr, const QSize &_size); /// \brief Before the scene graph starts to render, we update to the /// pending texture @@ -417,9 +454,6 @@ namespace plugins /// update signals: void PendingNewTexture(); - /// \brief OpenGL texture id - public: uint id = 0; - /// \brief Texture size public: QSize size = QSize(0, 0); @@ -429,12 +463,12 @@ namespace plugins /// \brief See RenderSync public: RenderSync &renderSync; - /// \brief Qt's scene graph texture - public: QSGTexture *texture = nullptr; - /// \brief Qt quick window public: QQuickWindow *window = nullptr; - }; + + /// \brief Pointer to render interface to handle OpenGL/Metal compatibility + private: std::unique_ptr rhi; + }; } } } diff --git a/src/plugins/minimal_scene/MinimalSceneRhi.cc b/src/plugins/minimal_scene/MinimalSceneRhi.cc new file mode 100644 index 000000000..284afd098 --- /dev/null +++ b/src/plugins/minimal_scene/MinimalSceneRhi.cc @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2021 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ + +#include "MinimalSceneRhi.hh" + +using namespace ignition; +using namespace gui; +using namespace plugins; + +///////////////////////////////////////////////// +IgnCameraTextureRhi::~IgnCameraTextureRhi() = default; + +///////////////////////////////////////////////// +RenderThreadRhi::~RenderThreadRhi() = default; + +///////////////////////////////////////////////// +QOffscreenSurface *RenderThreadRhi::Surface() const +{ + return static_cast(nullptr); +} + +///////////////////////////////////////////////// +void RenderThreadRhi::SetSurface(QOffscreenSurface *) +{ + /* no-op */ +} + +///////////////////////////////////////////////// +QOpenGLContext *RenderThreadRhi::Context() const +{ + return static_cast(nullptr); +} + +///////////////////////////////////////////////// +void RenderThreadRhi::SetContext(QOpenGLContext *) +{ + /* no-op */ +} + +///////////////////////////////////////////////// +TextureNodeRhi::~TextureNodeRhi() = default; diff --git a/src/plugins/minimal_scene/MinimalSceneRhi.hh b/src/plugins/minimal_scene/MinimalSceneRhi.hh new file mode 100644 index 000000000..42d3784a5 --- /dev/null +++ b/src/plugins/minimal_scene/MinimalSceneRhi.hh @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2021 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ + +#ifndef IGNITION_GUI_PLUGINS_MINIMALSCENE_MINIMALSCENERHI_HH_ +#define IGNITION_GUI_PLUGINS_MINIMALSCENE_MINIMALSCENERHI_HH_ + +#include "ignition/gui/Plugin.hh" +#include "ignition/rendering/RenderTypes.hh" + +#include +#include + +namespace ignition +{ +namespace gui +{ +namespace plugins +{ + class IgnCameraTextureRhi + { + public: virtual ~IgnCameraTextureRhi(); + public: virtual void Update(rendering::CameraPtr _camera) = 0; + public: virtual void TextureId(void* _texturePtr) = 0; + }; + + /// \brief Ign-rendering renderer. + class IgnRenderer; + class RenderSync; + + /// \brief Render interface class to handle OpenGL / Metal compatibility + class RenderThreadRhi + { + public: virtual ~RenderThreadRhi(); + public: virtual QOffscreenSurface *Surface() const; + public: virtual void SetSurface(QOffscreenSurface *_surface); + public: virtual QOpenGLContext *Context() const; + public: virtual void SetContext(QOpenGLContext *_context); + public: virtual void InitialiseOnMainThread() = 0; + public: virtual void RenderNext(RenderSync *_renderSync) = 0; + public: virtual void* TexturePtr() const = 0; + public: virtual QSize TextureSize() const = 0; + public: virtual void ShutDown() = 0; + }; + + /// \brief Render interface class to handle OpenGL / Metal compatibility + class TextureNodeRhi + { + public: virtual ~TextureNodeRhi(); + public: virtual QSGTexture *Texture() const = 0; + public: virtual bool HasNewTexture() const = 0; + public: virtual void NewTexture( + void* _texturePtr, const QSize &_size) = 0; + public: virtual void PrepareNode() = 0; + }; +} +} +} + +#endif diff --git a/src/plugins/minimal_scene/MinimalSceneRhiMetal.hh b/src/plugins/minimal_scene/MinimalSceneRhiMetal.hh new file mode 100644 index 000000000..b2093e8a5 --- /dev/null +++ b/src/plugins/minimal_scene/MinimalSceneRhiMetal.hh @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2021 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ + +#ifndef IGNITION_GUI_PLUGINS_MINIMALSCENE_MINIMALSCENERHIMETAL_HH_ +#define IGNITION_GUI_PLUGINS_MINIMALSCENE_MINIMALSCENERHIMETAL_HH_ + +#include "MinimalSceneRhi.hh" +#include "ignition/gui/Plugin.hh" + +#include +#include +#include + +#include + +namespace ignition +{ +namespace gui +{ +namespace plugins +{ + class IgnCameraTextureRhiMetalPrivate; + class IgnCameraTextureRhiMetal : public IgnCameraTextureRhi + { + public: virtual ~IgnCameraTextureRhiMetal() override; + public: IgnCameraTextureRhiMetal(); + public: virtual void Update(rendering::CameraPtr _camera) override; + public: virtual void TextureId(void* _texturePtr) override; + private: std::unique_ptr dataPtr; + }; + + class RenderThreadRhiMetalPrivate; + class RenderThreadRhiMetal : public RenderThreadRhi + { + public: virtual ~RenderThreadRhiMetal() override; + public: RenderThreadRhiMetal(IgnRenderer *_renderer); + public: virtual void InitialiseOnMainThread() override; + public: virtual void RenderNext(RenderSync *_renderSync) override; + public: virtual void* TexturePtr() const override; + public: virtual QSize TextureSize() const override; + public: virtual void ShutDown() override; + + private: RenderThreadRhiMetal( + const RenderThreadRhiMetal &_other) = delete; + private: RenderThreadRhiMetal& operator=( + const RenderThreadRhiMetal &_other) = delete; + private: std::unique_ptr dataPtr; + }; + + class TextureNodeRhiMetalPrivate; + class TextureNodeRhiMetal : public TextureNodeRhi + { + public: virtual ~TextureNodeRhiMetal() override; + public: TextureNodeRhiMetal(QQuickWindow *_window); + public: virtual QSGTexture *Texture() const override; + public: virtual bool HasNewTexture() const override; + public: virtual void NewTexture( + void* _texturePtr, const QSize &_size)override; + public: virtual void PrepareNode() override; + + private: TextureNodeRhiMetal( + const TextureNodeRhiMetal &_other) = delete; + private: TextureNodeRhiMetal& operator=( + const TextureNodeRhiMetal &_other) = delete; + private: std::unique_ptr dataPtr; + }; +} +} +} + +#endif diff --git a/src/plugins/minimal_scene/MinimalSceneRhiMetal.mm b/src/plugins/minimal_scene/MinimalSceneRhiMetal.mm new file mode 100644 index 000000000..6a9e8fe5f --- /dev/null +++ b/src/plugins/minimal_scene/MinimalSceneRhiMetal.mm @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2021 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ + +#include "MinimalSceneRhiMetal.hh" +#include "MinimalScene.hh" + +#include +#include + +#include +#include +#include +#include + +#include + +#include + +#if ! __has_feature(objc_arc) +#error "ARC is off" +#endif + +///////////////////////////////////////////////// +namespace ignition +{ +namespace gui +{ +namespace plugins +{ + class IgnCameraTextureRhiMetalPrivate + { + public: id metalTexture = nil; + }; + + class RenderThreadRhiMetalPrivate + { + public: IgnRenderer *renderer = nullptr; + public: void *texturePtr = nullptr; + }; + + class TextureNodeRhiMetalPrivate + { + public: id metalTexture = nil; + public: id newMetalTexture = nil; + public: QSize size {0, 0}; + public: QSize newSize {0, 0}; + public: QMutex mutex; + public: QSGTexture *texture = nullptr; + public: QQuickWindow *window = nullptr; + }; +} +} +} + +using namespace ignition; +using namespace gui; +using namespace plugins; + +///////////////////////////////////////////////// +IgnCameraTextureRhiMetal::~IgnCameraTextureRhiMetal() = default; + +///////////////////////////////////////////////// +IgnCameraTextureRhiMetal::IgnCameraTextureRhiMetal() + : dataPtr(std::make_unique()) +{ +} + +///////////////////////////////////////////////// +void IgnCameraTextureRhiMetal::Update(rendering::CameraPtr _camera) +{ + void *texturePtr = nullptr; + _camera->RenderTextureMetalId(&texturePtr); + this->dataPtr->metalTexture = CFBridgingRelease(texturePtr); +} + +///////////////////////////////////////////////// +void IgnCameraTextureRhiMetal::TextureId(void* _texturePtr) +{ + *static_cast(_texturePtr) = + (void*)CFBridgingRetain(this->dataPtr->metalTexture); +} + +///////////////////////////////////////////////// +///////////////////////////////////////////////// +RenderThreadRhiMetal::~RenderThreadRhiMetal() = default; + +///////////////////////////////////////////////// +RenderThreadRhiMetal::RenderThreadRhiMetal(IgnRenderer *_renderer) + : dataPtr(std::make_unique()) +{ + this->dataPtr->renderer = _renderer; +} + +///////////////////////////////////////////////// +void RenderThreadRhiMetal::InitialiseOnMainThread() +{ + this->dataPtr->renderer->InitialiseOnMainThread(); +} + +///////////////////////////////////////////////// +void RenderThreadRhiMetal::RenderNext(RenderSync *_renderSync) +{ + if (!this->dataPtr->renderer->initialized) + { + this->dataPtr->renderer->Initialize(); + } + + // Check if engine has been successfully initialized + if (!this->dataPtr->renderer->initialized) + { + ignerr << "Unable to initialize renderer" << std::endl; + return; + } + + // Call the renderer + this->dataPtr->renderer->Render(_renderSync); + + // Get reference to the rendered texture + this->dataPtr->texturePtr = nullptr; + this->dataPtr->renderer->TextureId(&this->dataPtr->texturePtr); +} + +///////////////////////////////////////////////// +void* RenderThreadRhiMetal::TexturePtr() const +{ + return this->dataPtr->texturePtr; +} + +///////////////////////////////////////////////// +QSize RenderThreadRhiMetal::TextureSize() const +{ + return this->dataPtr->renderer->textureSize; +} + +///////////////////////////////////////////////// +void RenderThreadRhiMetal::ShutDown() +{ + this->dataPtr->renderer->Destroy(); + + this->dataPtr->texturePtr = nullptr; +} + +///////////////////////////////////////////////// +///////////////////////////////////////////////// +TextureNodeRhiMetal::~TextureNodeRhiMetal() +{ + delete this->dataPtr->texture; + this->dataPtr->texture = nullptr; +} + +///////////////////////////////////////////////// +TextureNodeRhiMetal::TextureNodeRhiMetal(QQuickWindow *_window) + : dataPtr(std::make_unique()) +{ + this->dataPtr->window = _window; + + // Our texture node must have a texture, so use the default 0 texture. + this->dataPtr->texture = + this->dataPtr->window->createTextureFromNativeObject( + QQuickWindow::NativeObjectTexture, + static_cast(&this->dataPtr->metalTexture), + 0, + QSize(1, 1)); +} + +///////////////////////////////////////////////// +QSGTexture *TextureNodeRhiMetal::Texture() const +{ + return this->dataPtr->texture; +} + +///////////////////////////////////////////////// +bool TextureNodeRhiMetal::HasNewTexture() const +{ + return (this->dataPtr->newMetalTexture != nil); +} + +///////////////////////////////////////////////// +void TextureNodeRhiMetal::NewTexture( + void* _texturePtr, const QSize &_size) +{ + this->dataPtr->mutex.lock(); + this->dataPtr->metalTexture = CFBridgingRelease(_texturePtr); + this->dataPtr->size = _size; + this->dataPtr->mutex.unlock(); +} + +///////////////////////////////////////////////// +void TextureNodeRhiMetal::PrepareNode() +{ + this->dataPtr->mutex.lock(); + this->dataPtr->newMetalTexture = this->dataPtr->metalTexture; + this->dataPtr->newSize = this->dataPtr->size; + this->dataPtr->metalTexture = nil; + this->dataPtr->mutex.unlock(); + + if (this->dataPtr->newMetalTexture) + { + delete this->dataPtr->texture; + this->dataPtr->texture = nullptr; + + this->dataPtr->texture = + this->dataPtr->window->createTextureFromNativeObject( + QQuickWindow::NativeObjectTexture, + static_cast(&this->dataPtr->newMetalTexture), + 0, + this->dataPtr->newSize); + } +} diff --git a/src/plugins/minimal_scene/MinimalSceneRhiOpenGL.cc b/src/plugins/minimal_scene/MinimalSceneRhiOpenGL.cc new file mode 100644 index 000000000..a14675016 --- /dev/null +++ b/src/plugins/minimal_scene/MinimalSceneRhiOpenGL.cc @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2021 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ + +#include "MinimalSceneRhiOpenGL.hh" +#include "MinimalScene.hh" + +#include +#include + +#include +#include +#include +#include + +#include + +///////////////////////////////////////////////// +namespace ignition +{ +namespace gui +{ +namespace plugins +{ + class IgnCameraTextureRhiOpenGLPrivate + { + public: int textureId = 0; + }; + + class RenderThreadRhiOpenGLPrivate + { + public: IgnRenderer *renderer = nullptr; + public: void *texturePtr = nullptr; + public: QOffscreenSurface *surface = nullptr; + public: QOpenGLContext *context = nullptr; + }; + + class TextureNodeRhiOpenGLPrivate + { + public: int textureId = 0; + public: int newTextureId = 0; + public: QSize size {0, 0}; + public: QSize newSize {0, 0}; + public: QMutex mutex; + public: QSGTexture *texture = nullptr; + public: QQuickWindow *window = nullptr; + }; +} +} +} + +using namespace ignition; +using namespace gui; +using namespace plugins; + +///////////////////////////////////////////////// +IgnCameraTextureRhiOpenGL::~IgnCameraTextureRhiOpenGL() = default; + +///////////////////////////////////////////////// +IgnCameraTextureRhiOpenGL::IgnCameraTextureRhiOpenGL() + : dataPtr(std::make_unique()) +{ +} + +///////////////////////////////////////////////// +void IgnCameraTextureRhiOpenGL::Update(rendering::CameraPtr _camera) +{ + this->dataPtr->textureId = _camera->RenderTextureGLId(); +} + +///////////////////////////////////////////////// +void IgnCameraTextureRhiOpenGL::TextureId(void* _texturePtr) +{ + *static_cast(_texturePtr) = (void*)&this->dataPtr->textureId; +} + +///////////////////////////////////////////////// +///////////////////////////////////////////////// +RenderThreadRhiOpenGL::~RenderThreadRhiOpenGL() = default; + +///////////////////////////////////////////////// +RenderThreadRhiOpenGL::RenderThreadRhiOpenGL(IgnRenderer *_renderer) + : dataPtr(std::make_unique()) +{ + this->dataPtr->renderer = _renderer; +} + +///////////////////////////////////////////////// +QOffscreenSurface *RenderThreadRhiOpenGL::Surface() const +{ + return this->dataPtr->surface; +} + +///////////////////////////////////////////////// +void RenderThreadRhiOpenGL::SetSurface(QOffscreenSurface *_surface) +{ + this->dataPtr->surface = _surface; +} + +///////////////////////////////////////////////// +QOpenGLContext *RenderThreadRhiOpenGL::Context() const +{ + return this->dataPtr->context; +} + +///////////////////////////////////////////////// +void RenderThreadRhiOpenGL::SetContext(QOpenGLContext *_context) +{ + this->dataPtr->context = _context; +} + +///////////////////////////////////////////////// +void RenderThreadRhiOpenGL::InitialiseOnMainThread() +{ + this->dataPtr->context->makeCurrent(this->dataPtr->surface); + + this->dataPtr->renderer->InitialiseOnMainThread(); + + this->dataPtr->context->doneCurrent(); +} + +///////////////////////////////////////////////// +void RenderThreadRhiOpenGL::RenderNext(RenderSync *_renderSync) +{ + this->dataPtr->context->makeCurrent(this->dataPtr->surface); + + if (!this->dataPtr->renderer->initialized) + { + this->dataPtr->renderer->Initialize(); + } + + if (!this->dataPtr->renderer->initialized) + { + ignerr << "Unable to initialize renderer" << std::endl; + return; + } + + // Call the renderer + this->dataPtr->renderer->Render(_renderSync); + + // Get reference to the rendered texture + this->dataPtr->texturePtr = nullptr; + this->dataPtr->renderer->TextureId(&this->dataPtr->texturePtr); + + this->dataPtr->context->doneCurrent(); +} + +///////////////////////////////////////////////// +void* RenderThreadRhiOpenGL::TexturePtr() const +{ + return this->dataPtr->texturePtr; +} + +///////////////////////////////////////////////// +QSize RenderThreadRhiOpenGL::TextureSize() const +{ + return this->dataPtr->renderer->textureSize; +} + +///////////////////////////////////////////////// +void RenderThreadRhiOpenGL::ShutDown() +{ + this->dataPtr->renderer->Destroy(); + + this->dataPtr->texturePtr = nullptr; + + this->dataPtr->context->doneCurrent(); + delete this->dataPtr->context; + this->dataPtr->context = nullptr; + + // Schedule this to be deleted only after we're done cleaning up + this->dataPtr->surface->deleteLater(); +} + +///////////////////////////////////////////////// +///////////////////////////////////////////////// +TextureNodeRhiOpenGL::~TextureNodeRhiOpenGL() +{ + delete this->dataPtr->texture; + this->dataPtr->texture = nullptr; +} + +///////////////////////////////////////////////// +TextureNodeRhiOpenGL::TextureNodeRhiOpenGL(QQuickWindow *_window) + : dataPtr(std::make_unique()) +{ + this->dataPtr->window = _window; + + // Our texture node must have a texture, so use the default 0 texture. +#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) +# ifndef _WIN32 +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wdeprecated-declarations" +# endif + this->dataPtr->texture = this->dataPtr->window->createTextureFromId( + this->dataPtr->textureId, + QSize(1, 1), + QQuickWindow::TextureIsOpaque); +# ifndef _WIN32 +# pragma GCC diagnostic pop +# endif +#else + this->dataPtr->texture = + this->dataPtr->window->createTextureFromNativeObject( + QQuickWindow::NativeObjectTexture, + static_cast(&this->dataPtr->textureId), + 0, + QSize(1, 1)); +#endif +} + +///////////////////////////////////////////////// +QSGTexture *TextureNodeRhiOpenGL::Texture() const +{ + return this->dataPtr->texture; +} + +///////////////////////////////////////////////// +bool TextureNodeRhiOpenGL::HasNewTexture() const +{ + return (this->dataPtr->newTextureId != 0); +} + +///////////////////////////////////////////////// +void TextureNodeRhiOpenGL::NewTexture( + void* _texturePtr /*[in]*/, const QSize &_size) +{ + this->dataPtr->mutex.lock(); + this->dataPtr->textureId = *static_cast(_texturePtr); + this->dataPtr->size = _size; + this->dataPtr->mutex.unlock(); +} + +///////////////////////////////////////////////// +void TextureNodeRhiOpenGL::PrepareNode() +{ + this->dataPtr->mutex.lock(); + this->dataPtr->newTextureId = this->dataPtr->textureId; + this->dataPtr->newSize = this->dataPtr->size; + this->dataPtr->textureId = 0; + this->dataPtr->mutex.unlock(); + + if (this->dataPtr->newTextureId) + { + delete this->dataPtr->texture; + this->dataPtr->texture = nullptr; + +#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) +# ifndef _WIN32 +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wdeprecated-declarations" +# endif + this->dataPtr->texture = this->dataPtr->window->createTextureFromId( + this->dataPtr->newTextureId, + this->dataPtr->newSize, + QQuickWindow::TextureIsOpaque); +# ifndef _WIN32 +# pragma GCC diagnostic pop +# endif +#else + this->dataPtr->texture = + this->dataPtr->window->createTextureFromNativeObject( + QQuickWindow::NativeObjectTexture, + static_cast(&this->dataPtr->newTextureId), + 0, + this->dataPtr->newSize); +#endif + } +} diff --git a/src/plugins/minimal_scene/MinimalSceneRhiOpenGL.hh b/src/plugins/minimal_scene/MinimalSceneRhiOpenGL.hh new file mode 100644 index 000000000..29ad4250d --- /dev/null +++ b/src/plugins/minimal_scene/MinimalSceneRhiOpenGL.hh @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2021 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ + +#ifndef IGNITION_GUI_PLUGINS_MINIMALSCENE_TEXTURENODERHIOPENGL_HH_ +#define IGNITION_GUI_PLUGINS_MINIMALSCENE_TEXTURENODERHIOPENGL_HH_ + +#include "MinimalSceneRhi.hh" +#include "ignition/gui/Plugin.hh" + +#include +#include +#include + +#include + +namespace ignition +{ +namespace gui +{ +namespace plugins +{ + class IgnCameraTextureRhiOpenGLPrivate; + class IgnCameraTextureRhiOpenGL : public IgnCameraTextureRhi + { + public: virtual ~IgnCameraTextureRhiOpenGL() override; + public: IgnCameraTextureRhiOpenGL(); + public: virtual void Update(rendering::CameraPtr _camera) override; + public: virtual void TextureId(void* _texturePtr) override; + private: std::unique_ptr dataPtr; + }; + + class RenderThreadRhiOpenGLPrivate; + class RenderThreadRhiOpenGL : public RenderThreadRhi + { + public: virtual ~RenderThreadRhiOpenGL() override; + public: RenderThreadRhiOpenGL(IgnRenderer *_renderer); + public: virtual QOffscreenSurface *Surface() const override; + public: virtual void SetSurface(QOffscreenSurface *_surface) override; + public: virtual QOpenGLContext *Context() const override; + public: virtual void SetContext(QOpenGLContext *_context) override; + public: virtual void InitialiseOnMainThread() override; + public: virtual void RenderNext(RenderSync *_renderSync) override; + public: virtual void* TexturePtr() const override; + public: virtual QSize TextureSize() const override; + public: virtual void ShutDown() override; + + private: RenderThreadRhiOpenGL( + const RenderThreadRhiOpenGL &_other) = delete; + private: RenderThreadRhiOpenGL& operator=( + const RenderThreadRhiOpenGL &_other) = delete; + private: std::unique_ptr dataPtr; + }; + + class TextureNodeRhiOpenGLPrivate; + class TextureNodeRhiOpenGL : public TextureNodeRhi + { + public: virtual ~TextureNodeRhiOpenGL() override; + public: TextureNodeRhiOpenGL(QQuickWindow *_window); + public: virtual QSGTexture *Texture() const override; + public: virtual bool HasNewTexture() const override; + public: virtual void NewTexture( + void* _texturePtr, const QSize &_size) override; + public: virtual void PrepareNode() override; + + private: TextureNodeRhiOpenGL( + const TextureNodeRhiOpenGL &_other) = delete; + private: TextureNodeRhiOpenGL& operator=( + const TextureNodeRhiOpenGL &_other) = delete; + private: std::unique_ptr dataPtr; + }; +} +} +} + +#endif diff --git a/src/plugins/scene3d/CMakeLists.txt b/src/plugins/scene3d/CMakeLists.txt index 5679bfcf7..255ab5896 100644 --- a/src/plugins/scene3d/CMakeLists.txt +++ b/src/plugins/scene3d/CMakeLists.txt @@ -1,9 +1,20 @@ -ign_gui_add_plugin(Scene3D - SOURCES - Scene3D.cc - QT_HEADERS - Scene3D.hh - PUBLIC_LINK_LIBS - ignition-rendering${IGN_RENDERING_VER}::ignition-rendering${IGN_RENDERING_VER} -) +# TODO(srmainwaring): support both OpenGL/Metal +if (APPLE) + ign_gui_add_plugin(Scene3D + SOURCES + Scene3D.mm + QT_HEADERS + Scene3D.hh + PUBLIC_LINK_LIBS + ignition-rendering${IGN_RENDERING_VER}::ignition-rendering${IGN_RENDERING_VER} + "-framework AppKit" + "-framework Metal" + ) + set_source_files_properties( + Scene3D.mm + PROPERTIES + COMPILE_FLAGS + "-fobjc-arc" + ) +endif() diff --git a/src/plugins/scene3d/Scene3D.cc b/src/plugins/scene3d/Scene3D.cc index d601b8d72..e94d0cf42 100644 --- a/src/plugins/scene3d/Scene3D.cc +++ b/src/plugins/scene3d/Scene3D.cc @@ -1406,7 +1406,7 @@ void RenderWindowItem::Ready() this->connect(this, &QQuickItem::heightChanged, this->dataPtr->renderThread, &RenderThread::SizeChanged); - this->dataPtr->renderThread->start(); +// this->dataPtr->renderThread->start(); this->update(); } @@ -1428,8 +1428,8 @@ QSGNode *RenderWindowItem::updatePaintNode(QSGNode *_node, this->dataPtr->renderThread->context->setFormat(current->format()); this->dataPtr->renderThread->context->setShareContext(current); this->dataPtr->renderThread->context->create(); - this->dataPtr->renderThread->context->moveToThread( - this->dataPtr->renderThread); + // this->dataPtr->renderThread->context->moveToThread( + // this->dataPtr->renderThread); current->makeCurrent(this->window()); @@ -1467,6 +1467,13 @@ QSGNode *RenderWindowItem::updatePaintNode(QSGNode *_node, this->connect(node, &TextureNode::TextureInUse, this->dataPtr->renderThread, &RenderThread::RenderNext, Qt::QueuedConnection); + // init ogre before moving to render thread + this->dataPtr->renderThread->RenderNext(); + + this->dataPtr->renderThread->context->moveToThread( + this->dataPtr->renderThread); + this->dataPtr->renderThread->start(); + // Get the production of FBO textures started.. QMetaObject::invokeMethod(this->dataPtr->renderThread, "RenderNext", Qt::QueuedConnection); diff --git a/src/plugins/scene3d/Scene3D.hh b/src/plugins/scene3d/Scene3D.hh index 468bd295b..cc47d9f7e 100644 --- a/src/plugins/scene3d/Scene3D.hh +++ b/src/plugins/scene3d/Scene3D.hh @@ -109,6 +109,10 @@ namespace plugins /// \brief Initialize the render engine public: void Initialize(); + /// \brief Initialise the render engine and scene. + /// Must be called on the main thread. + public: void InitialiseOnMainThread(); + /// \brief Destroy camera associated with this renderer public: void Destroy(); @@ -158,11 +162,12 @@ namespace plugins private: math::Vector3d ScreenToScene(const math::Vector2i &_screenPos) const; + // TODO(srmainwaring): OpenGL/Metal /// \brief Render texture id - public: GLuint textureId = 0u; + public: void MTLTexture(void* _texturePtr /*[out]*/); /// \brief Render engine to use - public: std::string engineName = "ogre"; + public: std::string engineName = "ogre2"; /// \brief Unique scene name public: std::string sceneName = "scene"; @@ -224,17 +229,23 @@ namespace plugins /// \brief Slot called to update render texture size public slots: void SizeChanged(); + // TODO(srmainwaring): OpenGL/Metal /// \brief Signal to indicate that a frame has been rendered and ready /// to be displayed - /// \param[in] _id GLuid of the opengl texture + /// \param[in] _texturePtr Pointer to a texture Id /// \param[in] _size Size of the texture - signals: void TextureReady(int _id, const QSize &_size); + signals: void TextureReady(void* _texturePtr, const QSize &_size); + // TODO(srmainwaring): OpenGL/Metal /// \brief Offscreen surface to render to - public: QOffscreenSurface *surface = nullptr; + // public: QOffscreenSurface *surface = nullptr; + // TODO(srmainwaring): OpenGL/Metal /// \brief OpenGL context to be passed to the render engine - public: QOpenGLContext *context = nullptr; + // public: QOpenGLContext *context = nullptr; + public: bool initialised = false; + + void InitialiseOnMainThread(); /// \brief Ign-rendering renderer public: IgnRenderer ignRenderer; @@ -344,6 +355,10 @@ namespace plugins private: std::unique_ptr dataPtr; }; + + // TODO(srmainwaring): OpenGL/Metal + class TextureNodePrivate; + /// \brief Texture node for displaying the render texture from ign-renderer class TextureNode : public QObject, public QSGSimpleTextureNode { @@ -356,11 +371,12 @@ namespace plugins /// \brief Destructor public: ~TextureNode() override; + // TODO(srmainwaring): OpenGL/Metal /// \brief This function gets called on the FBO rendering thread and will /// store the texture id and size and schedule an update on the window. - /// \param[in] _id OpenGL render texture Id + /// \param[in] _texturePtr Pointer to render texture Id /// \param[in] _size Texture size - public slots: void NewTexture(int _id, const QSize &_size); + public slots: void NewTexture(void* _texturePtr, const QSize &_size); /// \brief Before the scene graph starts to render, we update to the /// pending texture @@ -374,8 +390,9 @@ namespace plugins /// update signals: void PendingNewTexture(); + // TODO(srmainwaring): OpenGL/Metal /// \brief OpenGL texture id - public: int id = 0; + // public: int id = 0; /// \brief Texture size public: QSize size = QSize(0, 0); @@ -388,6 +405,9 @@ namespace plugins /// \brief Qt quick window public: QQuickWindow *window = nullptr; + + // TODO(srmainwaring): OpenGL/Metal + private: std::unique_ptr dataPtr; }; } } diff --git a/src/plugins/scene3d/Scene3D.mm b/src/plugins/scene3d/Scene3D.mm new file mode 100644 index 000000000..6ada1b038 --- /dev/null +++ b/src/plugins/scene3d/Scene3D.mm @@ -0,0 +1,1998 @@ +/* + * Copyright (C) 2017 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ + +#include "Scene3D.hh" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include + +// TODO(louise) Remove these pragmas once ign-rendering and ign-msgs +// are disabling the warnings +#ifdef _MSC_VER +#pragma warning(push, 0) +#endif +#include + +#include +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#include + +#include "ignition/gui/Application.hh" +#include "ignition/gui/Conversions.hh" +#include "ignition/gui/GuiEvents.hh" +#include "ignition/gui/MainWindow.hh" + +#include + +//----------------------------------------------------------------------- +void BuildScene(ignition::rendering::ScenePtr _scene) +{ + using namespace ignition; + using namespace rendering; + + // initialize _scene + _scene->SetAmbientLight(0.3, 0.3, 0.3); + VisualPtr root = _scene->RootVisual(); + + // create directional light + DirectionalLightPtr light0 = _scene->CreateDirectionalLight(); + light0->SetDirection(-0.5, 0.5, -1); + light0->SetDiffuseColor(0.5, 0.5, 0.5); + light0->SetSpecularColor(0.5, 0.5, 0.5); + root->AddChild(light0); + + // create point light + PointLightPtr light2 = _scene->CreatePointLight(); + light2->SetDiffuseColor(0.5, 0.5, 0.5); + light2->SetSpecularColor(0.5, 0.5, 0.5); + light2->SetLocalPosition(3, 5, 5); + root->AddChild(light2); + + // create green material + MaterialPtr green = _scene->CreateMaterial(); + green->SetAmbient(0.0, 0.5, 0.0); + green->SetDiffuse(0.0, 0.7, 0.0); + green->SetSpecular(0.5, 0.5, 0.5); + green->SetShininess(50); + green->SetReflectivity(0); + + // create center visual + VisualPtr center = _scene->CreateVisual(); + center->AddGeometry(_scene->CreateSphere()); + center->SetLocalPosition(3, 0, 0); + center->SetLocalScale(0.1, 0.1, 0.1); + center->SetMaterial(green); + root->AddChild(center); + + // create red material + MaterialPtr red = _scene->CreateMaterial(); + red->SetAmbient(0.5, 0.0, 0.0); + red->SetDiffuse(1.0, 0.0, 0.0); + red->SetSpecular(0.5, 0.5, 0.5); + red->SetShininess(50); + red->SetReflectivity(0); + red->SetRenderOrder(3); + + // create sphere visual + VisualPtr sphere = _scene->CreateVisual(); + sphere->AddGeometry(_scene->CreateSphere()); + sphere->SetOrigin(0.0, -0.5, 0.0); + sphere->SetLocalPosition(3, 0, 0); + sphere->SetLocalRotation(0, 0, 0); + sphere->SetLocalScale(1, 1, 1); + sphere->SetMaterial(red); + root->AddChild(sphere); + + // create blue material + MaterialPtr blue = _scene->CreateMaterial(); + blue->SetAmbient(0.0, 0.0, 0.3); + blue->SetDiffuse(0.0, 0.0, 0.8); + blue->SetSpecular(0.5, 0.5, 0.5); + blue->SetShininess(50); + blue->SetReflectivity(0); + + // create box visual + VisualPtr box = _scene->CreateVisual(); + box->AddGeometry(_scene->CreateBox()); + box->SetOrigin(0.0, 0.5, 0.0); + box->SetLocalPosition(3, 0, 0); + box->SetLocalRotation(IGN_PI / 4, 0, IGN_PI / 3); + box->SetLocalScale(1, 2.5, 1); + box->SetMaterial(blue); + root->AddChild(box); + + // create ellipsoid visual + VisualPtr ellipsoidVisual = _scene->CreateVisual(); + auto ellipsoid = _scene->CreateSphere(); + ellipsoidVisual->SetLocalScale(1.2, 0.7, 0.5); + ellipsoidVisual->AddGeometry(ellipsoid); + ellipsoidVisual->SetLocalPosition(3, -1, 0); + ellipsoidVisual->SetMaterial(green); + root->AddChild(ellipsoidVisual); + + // create white material + MaterialPtr white = _scene->CreateMaterial(); + white->SetAmbient(0.5, 0.5, 0.5); + white->SetDiffuse(0.8, 0.8, 0.8); + white->SetReceiveShadows(true); + white->SetReflectivity(0); + white->SetRenderOrder(0); + + VisualPtr capsuleVisual = _scene->CreateVisual(); + CapsulePtr capsule = _scene->CreateCapsule(); + capsule->SetLength(0.2); + capsule->SetRadius(0.2); + capsuleVisual->AddGeometry(capsule); + capsuleVisual->SetOrigin(0.0, 0.0, 0.0); + capsuleVisual->SetLocalPosition(4, 2, 0); + capsuleVisual->SetLocalScale(1, 1, 1); + capsuleVisual->SetMaterial(red); + root->AddChild(capsuleVisual); + + // create plane visual + VisualPtr plane = _scene->CreateVisual(); + plane->AddGeometry(_scene->CreatePlane()); + plane->SetLocalScale(5, 8, 1); + plane->SetLocalPosition(3, 0, -0.5); + plane->SetMaterial(white); + root->AddChild(plane); + + // create plane visual + VisualPtr plane2 = _scene->CreateVisual(); + plane2->AddGeometry(_scene->CreatePlane()); + plane2->SetLocalScale(5, 8, 1); + plane2->SetLocalPosition(4, 0.5, -0.5); + plane2->Scale(0.1, 0.1, 1); + plane2->SetMaterial(red); + root->AddChild(plane2); + + // create axis visual + // VisualPtr axis = _scene->CreateAxisVisual(); + // axis->SetLocalPosition(4.0, 0.5, -0.4); + // root->AddChild(axis); + + // create camera + CameraPtr camera = _scene->CreateCamera("camera"); + camera->SetLocalPosition(0.0, 0.0, 0.0); + camera->SetLocalRotation(0.0, 0.0, 0.0); + camera->SetImageWidth(800); + camera->SetImageHeight(600); + camera->SetAntiAliasing(2); + camera->SetAspectRatio(800.0/600.0); + camera->SetHFOV(IGN_PI / 2); + root->AddChild(camera); + + // track target + camera->SetTrackTarget(box); +} + +namespace ignition +{ +namespace gui +{ +namespace plugins +{ + /// \brief Scene manager class for loading and managing objects in the scene + class SceneManager + { + /// \brief Constructor + public: SceneManager(); + + /// \brief Constructor + /// \param[in] _service Ign transport scene service name + /// \param[in] _poseTopic Ign transport pose topic name + /// \param[in] _deletionTopic Ign transport deletion topic name + /// \param[in] _sceneTopic Ign transport scene topic name + /// \param[in] _scene Pointer to the rendering scene + public: SceneManager(const std::string &_service, + const std::string &_poseTopic, + const std::string &_deletionTopic, + const std::string &_sceneTopic, + rendering::ScenePtr _scene); + + /// \brief Load the scene manager + /// \param[in] _service Ign transport service name + /// \param[in] _poseTopic Ign transport pose topic name + /// \param[in] _deletionTopic Ign transport deletion topic name + /// \param[in] _sceneTopic Ign transport scene topic name + /// \param[in] _scene Pointer to the rendering scene + public: void Load(const std::string &_service, + const std::string &_poseTopic, + const std::string &_deletionTopic, + const std::string &_sceneTopic, + rendering::ScenePtr _scene); + + /// \brief Make the scene service request and populate the scene + public: void Request(); + + /// \brief Update the scene based on pose msgs received + public: void Update(); + + /// \brief Callback function for the pose topic + /// \param[in] _msg Pose vector msg + private: void OnPoseVMsg(const msgs::Pose_V &_msg); + + /// \brief Load the scene from a scene msg + /// \param[in] _msg Scene msg + private: void LoadScene(const msgs::Scene &_msg); + + /// \brief Callback function for the request topic + /// \param[in] _msg Deletion message + private: void OnDeletionMsg(const msgs::UInt32_V &_msg); + + /// \brief Load the scene from a scene msg + /// \param[in] _msg Scene msg + private: void OnSceneSrvMsg(const msgs::Scene &_msg, const bool result); + + /// \brief Called when there's an entity is added to the scene + /// \param[in] _msg Scene msg + private: void OnSceneMsg(const msgs::Scene &_msg); + + /// \brief Load the model from a model msg + /// \param[in] _msg Model msg + /// \return Model visual created from the msg + private: rendering::VisualPtr LoadModel(const msgs::Model &_msg); + + /// \brief Load a link from a link msg + /// \param[in] _msg Link msg + /// \return Link visual created from the msg + private: rendering::VisualPtr LoadLink(const msgs::Link &_msg); + + /// \brief Load a visual from a visual msg + /// \param[in] _msg Visual msg + /// \return Visual visual created from the msg + private: rendering::VisualPtr LoadVisual(const msgs::Visual &_msg); + + /// \brief Load a geometry from a geometry msg + /// \param[in] _msg Geometry msg + /// \param[out] _scale Geometry scale that will be set based on msg param + /// \param[out] _localPose Additional local pose to be applied after the + /// visual's pose + /// \return Geometry object created from the msg + private: rendering::GeometryPtr LoadGeometry(const msgs::Geometry &_msg, + math::Vector3d &_scale, math::Pose3d &_localPose); + + /// \brief Load a material from a material msg + /// \param[in] _msg Material msg + /// \return Material object created from the msg + private: rendering::MaterialPtr LoadMaterial(const msgs::Material &_msg); + + /// \brief Load a light from a light msg + /// \param[in] _msg Light msg + /// \return Light object created from the msg + private: rendering::LightPtr LoadLight(const msgs::Light &_msg); + + /// \brief Delete an entity + /// \param[in] _entity Entity to delete + private: void DeleteEntity(const unsigned int _entity); + + //// \brief Ign-transport scene service name + private: std::string service; + + //// \brief Ign-transport pose topic name + private: std::string poseTopic; + + //// \brief Ign-transport deletion topic name + private: std::string deletionTopic; + + //// \brief Ign-transport scene topic name + private: std::string sceneTopic; + + //// \brief Pointer to the rendering scene + private: rendering::ScenePtr scene; + + //// \brief Mutex to protect the pose msgs + private: std::mutex mutex; + + /// \brief Map of entity id to pose + private: std::map poses; + + /// \brief Map of entity id to initial local poses + /// This is currently used to handle the normal vector in plane visuals. In + /// general, this can be used to store any local transforms between the + /// parent Visual and geometry. + private: std::map localPoses; + + /// \brief Map of visual id to visual pointers. + private: std::map visuals; + + /// \brief Map of light id to light pointers. + private: std::map lights; + + /// Entities to be deleted + private: std::vector toDeleteEntities; + + /// \brief Keeps the a list of unprocessed scene messages + private: std::vector sceneMsgs; + + /// \brief Transport node for making service request and subscribing to + /// pose topic + private: ignition::transport::Node node; + }; + + /// \brief Private data class for IgnRenderer + class IgnRendererPrivate + { + /// \brief Flag to indicate if mouse event is dirty + public: bool mouseDirty = false; + + /// \brief Flag to indicate if hover event is dirty + public: bool hoverDirty = false; + + /// \brief Mouse event + public: common::MouseEvent mouseEvent; + + /// \brief Key event + public: common::KeyEvent keyEvent; + + /// \brief Mouse move distance since last event. + public: math::Vector2d drag; + + /// \brief Mutex to protect mouse events + public: std::mutex mutex; + + /// \brief User camera + public: rendering::CameraPtr camera; + + /// \brief Camera orbit controller + public: rendering::OrbitViewController viewControl; + + /// \brief The currently hovered mouse position in screen coordinates + public: math::Vector2i mouseHoverPos{math::Vector2i::Zero}; + + /// \brief Ray query for mouse clicks + public: rendering::RayQueryPtr rayQuery; + + /// \brief Scene requester to get scene info + public: SceneManager sceneManager; + + /// \brief View control focus target + public: math::Vector3d target; + + // TODO: OpenGL/Metal + public: id metalTexture = nil; + }; + + /// \brief Private data class for RenderWindowItem + class RenderWindowItemPrivate + { + /// \brief Keep latest mouse event + public: common::MouseEvent mouseEvent; + + /// \brief Render thread + public : RenderThread *renderThread = nullptr; + + //// \brief List of threads + public: static QList threads; + }; + + /// \brief Private data class for Scene3D + class Scene3DPrivate + { + }; +} +} +} + +using namespace ignition; +using namespace gui; +using namespace plugins; + +QList RenderWindowItemPrivate::threads; + +///////////////////////////////////////////////// +SceneManager::SceneManager() +{ +} + +///////////////////////////////////////////////// +SceneManager::SceneManager(const std::string &_service, + const std::string &_poseTopic, + const std::string &_deletionTopic, + const std::string &_sceneTopic, + rendering::ScenePtr _scene) +{ + this->Load(_service, _poseTopic, _deletionTopic, _sceneTopic, _scene); +} + +///////////////////////////////////////////////// +void SceneManager::Load(const std::string &_service, + const std::string &_poseTopic, + const std::string &_deletionTopic, + const std::string &_sceneTopic, + rendering::ScenePtr _scene) +{ + this->service = _service; + this->poseTopic = _poseTopic; + this->deletionTopic = _deletionTopic; + this->sceneTopic = _sceneTopic; + this->scene = _scene; +} + +///////////////////////////////////////////////// +void SceneManager::Request() +{ + // wait for the service to be advertized + std::vector publishers; + const std::chrono::duration sleepDuration{1.0}; + const std::size_t tries = 30; + for (std::size_t i = 0; i < tries; ++i) + { + this->node.ServiceInfo(this->service, publishers); + if (publishers.size() > 0) + break; + std::this_thread::sleep_for(sleepDuration); + igndbg << "Waiting for service " << this->service << "\n"; + } + + if (publishers.empty() || + !this->node.Request(this->service, &SceneManager::OnSceneSrvMsg, this)) + { + ignerr << "Error making service request to " << this->service << std::endl; + } +} + +///////////////////////////////////////////////// +void SceneManager::OnPoseVMsg(const msgs::Pose_V &_msg) +{ + std::lock_guard lock(this->mutex); + for (int i = 0; i < _msg.pose_size(); ++i) + { + math::Pose3d pose = msgs::Convert(_msg.pose(i)); + + // apply additional local poses if available + const auto it = this->localPoses.find(_msg.pose(i).id()); + if (it != this->localPoses.end()) + { + pose = pose * it->second; + } + + this->poses[_msg.pose(i).id()] = pose; + } +} + +///////////////////////////////////////////////// +void SceneManager::OnDeletionMsg(const msgs::UInt32_V &_msg) +{ + std::lock_guard lock(this->mutex); + std::copy(_msg.data().begin(), _msg.data().end(), + std::back_inserter(this->toDeleteEntities)); +} + +///////////////////////////////////////////////// +void SceneManager::Update() +{ + // process msgs + std::lock_guard lock(this->mutex); + + for (const auto &msg : this->sceneMsgs) + { + this->LoadScene(msg); + } + this->sceneMsgs.clear(); + + for (const auto &entity : this->toDeleteEntities) + { + this->DeleteEntity(entity); + } + this->toDeleteEntities.clear(); + + + for (auto pIt = this->poses.begin(); pIt != this->poses.end();) + { + auto vIt = this->visuals.find(pIt->first); + if (vIt != this->visuals.end()) + { + auto visual = vIt->second.lock(); + if (visual) + { + visual->SetLocalPose(pIt->second); + } + else + { + this->visuals.erase(vIt); + } + this->poses.erase(pIt++); + } + else + { + auto lIt = this->lights.find(pIt->first); + if (lIt != this->lights.end()) + { + auto light = lIt->second.lock(); + if (light) + { + light->SetLocalPose(pIt->second); + } + else + { + this->lights.erase(lIt); + } + this->poses.erase(pIt++); + } + else + { + ++pIt; + } + } + } + + // Note we are clearing the pose msgs here but later on we may need to + // consider the case where pose msgs arrive before scene/visual msgs + this->poses.clear(); +} + + +///////////////////////////////////////////////// +void SceneManager::OnSceneMsg(const msgs::Scene &_msg) +{ + std::lock_guard lock(this->mutex); + this->sceneMsgs.push_back(_msg); +} + +///////////////////////////////////////////////// +void SceneManager::OnSceneSrvMsg(const msgs::Scene &_msg, const bool result) +{ + if (!result) + { + ignerr << "Error making service request to " << this->service + << std::endl; + return; + } + + { + std::lock_guard lock(this->mutex); + this->sceneMsgs.push_back(_msg); + } + + if (!this->poseTopic.empty()) + { + if (!this->node.Subscribe(this->poseTopic, &SceneManager::OnPoseVMsg, this)) + { + ignerr << "Error subscribing to pose topic: " << this->poseTopic + << std::endl; + } + } + else + { + ignwarn << "The pose topic, set via , for the Scene3D plugin " + << "is missing or empty. Please set this topic so that the Scene3D " + << "can receive and process pose information.\n"; + } + + if (!this->deletionTopic.empty()) + { + if (!this->node.Subscribe(this->deletionTopic, &SceneManager::OnDeletionMsg, + this)) + { + ignerr << "Error subscribing to deletion topic: " << this->deletionTopic + << std::endl; + } + } + else + { + ignwarn << "The deletion topic, set via , for the " + << "Scene3D plugin is missing or empty. Please set this topic so that " + << "the Scene3D can receive and process deletion information.\n"; + } + + if (!this->sceneTopic.empty()) + { + if (!this->node.Subscribe( + this->sceneTopic, &SceneManager::OnSceneMsg, this)) + { + ignerr << "Error subscribing to scene topic: " << this->sceneTopic + << std::endl; + } + } + else + { + ignwarn << "The scene topic, set via , for the " + << "Scene3D plugin is missing or empty. Please set this topic so that " + << "the Scene3D can receive and process scene information.\n"; + } +} + +void SceneManager::LoadScene(const msgs::Scene &_msg) +{ + rendering::VisualPtr rootVis = this->scene->RootVisual(); + + // load models + for (int i = 0; i < _msg.model_size(); ++i) + { + // Only add if it's not already loaded + if (this->visuals.find(_msg.model(i).id()) == this->visuals.end()) + { + rendering::VisualPtr modelVis = this->LoadModel(_msg.model(i)); + if (modelVis) + rootVis->AddChild(modelVis); + else + ignerr << "Failed to load model: " << _msg.model(i).name() << std::endl; + } + } + + // load lights + for (int i = 0; i < _msg.light_size(); ++i) + { + if (this->lights.find(_msg.light(i).id()) == this->lights.end()) + { + rendering::LightPtr light = this->LoadLight(_msg.light(i)); + if (light) + rootVis->AddChild(light); + else + ignerr << "Failed to load light: " << _msg.light(i).name() << std::endl; + } + } +} + +///////////////////////////////////////////////// +rendering::VisualPtr SceneManager::LoadModel(const msgs::Model &_msg) +{ + rendering::VisualPtr modelVis = this->scene->CreateVisual(); + if (_msg.has_pose()) + modelVis->SetLocalPose(msgs::Convert(_msg.pose())); + this->visuals[_msg.id()] = modelVis; + + // load links + for (int i = 0; i < _msg.link_size(); ++i) + { + rendering::VisualPtr linkVis = this->LoadLink(_msg.link(i)); + if (linkVis) + modelVis->AddChild(linkVis); + else + ignerr << "Failed to load link: " << _msg.link(i).name() << std::endl; + } + + // load nested models + for (int i = 0; i < _msg.model_size(); ++i) + { + rendering::VisualPtr nestedModelVis = this->LoadModel(_msg.model(i)); + if (nestedModelVis) + modelVis->AddChild(nestedModelVis); + else + ignerr << "Failed to load nested model: " << _msg.model(i).name() + << std::endl; + } + + return modelVis; +} + +///////////////////////////////////////////////// +rendering::VisualPtr SceneManager::LoadLink(const msgs::Link &_msg) +{ + rendering::VisualPtr linkVis = this->scene->CreateVisual(); + if (_msg.has_pose()) + linkVis->SetLocalPose(msgs::Convert(_msg.pose())); + this->visuals[_msg.id()] = linkVis; + + // load visuals + for (int i = 0; i < _msg.visual_size(); ++i) + { + rendering::VisualPtr visualVis = this->LoadVisual(_msg.visual(i)); + if (visualVis) + linkVis->AddChild(visualVis); + else + ignerr << "Failed to load visual: " << _msg.visual(i).name() << std::endl; + } + + // load lights + for (int i = 0; i < _msg.light_size(); ++i) + { + rendering::LightPtr light = this->LoadLight(_msg.light(i)); + if (light) + linkVis->AddChild(light); + else + ignerr << "Failed to load light: " << _msg.light(i).name() << std::endl; + } + + return linkVis; +} + +///////////////////////////////////////////////// +rendering::VisualPtr SceneManager::LoadVisual(const msgs::Visual &_msg) +{ + if (!_msg.has_geometry()) + return rendering::VisualPtr(); + + rendering::VisualPtr visualVis = this->scene->CreateVisual(); + this->visuals[_msg.id()] = visualVis; + + math::Vector3d scale = math::Vector3d::One; + math::Pose3d localPose; + rendering::GeometryPtr geom = + this->LoadGeometry(_msg.geometry(), scale, localPose); + + if (_msg.has_pose()) + visualVis->SetLocalPose(msgs::Convert(_msg.pose()) * localPose); + else + visualVis->SetLocalPose(localPose); + + if (geom) + { + // store the local pose + this->localPoses[_msg.id()] = localPose; + + visualVis->AddGeometry(geom); + visualVis->SetLocalScale(scale); + + // set material + rendering::MaterialPtr material{nullptr}; + if (_msg.has_material()) + { + material = this->LoadMaterial(_msg.material()); + } + // Don't set a default material for meshes because they + // may have their own + // TODO(anyone) support overriding mesh material + else if (!_msg.geometry().has_mesh()) + { + // create default material + material = this->scene->Material("ign-grey"); + if (!material) + { + material = this->scene->CreateMaterial("ign-grey"); + material->SetAmbient(0.3, 0.3, 0.3); + material->SetDiffuse(0.7, 0.7, 0.7); + material->SetSpecular(1.0, 1.0, 1.0); + material->SetRoughness(0.2f); + material->SetMetalness(1.0f); + } + } + else + { + // meshes created by mesh loader may have their own materials + // update/override their properties based on input sdf element values + auto mesh = std::dynamic_pointer_cast(geom); + for (unsigned int i = 0; i < mesh->SubMeshCount(); ++i) + { + auto submesh = mesh->SubMeshByIndex(i); + auto submeshMat = submesh->Material(); + if (submeshMat) + { + double productAlpha = (1.0-_msg.transparency()) * + (1.0 - submeshMat->Transparency()); + submeshMat->SetTransparency(1 - productAlpha); + submeshMat->SetCastShadows(_msg.cast_shadows()); + } + } + } + + if (material) + { + // set transparency + material->SetTransparency(_msg.transparency()); + + // cast shadows + material->SetCastShadows(_msg.cast_shadows()); + + geom->SetMaterial(material); + // todo(anyone) SetMaterial function clones the input material. + // but does not take ownership of it so we need to destroy it here. + // This is not ideal. We should let ign-rendering handle the lifetime + // of this material + this->scene->DestroyMaterial(material); + } + } + else + { + ignerr << "Failed to load geometry for visual: " << _msg.name() + << std::endl; + } + + return visualVis; +} + +///////////////////////////////////////////////// +rendering::GeometryPtr SceneManager::LoadGeometry(const msgs::Geometry &_msg, + math::Vector3d &_scale, math::Pose3d &_localPose) +{ + math::Vector3d scale = math::Vector3d::One; + math::Pose3d localPose = math::Pose3d::Zero; + rendering::GeometryPtr geom{nullptr}; + if (_msg.has_box()) + { + geom = this->scene->CreateBox(); + if (_msg.box().has_size()) + scale = msgs::Convert(_msg.box().size()); + } + else if (_msg.has_cylinder()) + { + geom = this->scene->CreateCylinder(); + scale.X() = _msg.cylinder().radius() * 2; + scale.Y() = scale.X(); + scale.Z() = _msg.cylinder().length(); + } + else if (_msg.has_capsule()) + { + auto capsule = this->scene->CreateCapsule(); + capsule->SetRadius(_msg.capsule().radius()); + capsule->SetLength(_msg.capsule().length()); + geom = capsule; + + scale.X() = _msg.capsule().radius() * 2; + scale.Y() = scale.X(); + scale.Z() = _msg.capsule().length() + scale.X(); + } + else if (_msg.has_ellipsoid()) + { + geom = this->scene->CreateSphere(); + scale.X() = _msg.ellipsoid().radii().x() * 2; + scale.Y() = _msg.ellipsoid().radii().y() * 2; + scale.Z() = _msg.ellipsoid().radii().z() * 2; + } + else if (_msg.has_plane()) + { + geom = this->scene->CreatePlane(); + + if (_msg.plane().has_size()) + { + scale.X() = _msg.plane().size().x(); + scale.Y() = _msg.plane().size().y(); + } + + if (_msg.plane().has_normal()) + { + // Create a rotation for the plane mesh to account for the normal vector. + // The rotation is the angle between the +z(0,0,1) vector and the + // normal, which are both expressed in the local (Visual) frame. + math::Vector3d normal = msgs::Convert(_msg.plane().normal()); + localPose.Rot().From2Axes(math::Vector3d::UnitZ, normal.Normalized()); + } + } + else if (_msg.has_sphere()) + { + geom = this->scene->CreateSphere(); + scale.X() = _msg.sphere().radius() * 2; + scale.Y() = scale.X(); + scale.Z() = scale.X(); + } + else if (_msg.has_mesh()) + { + if (_msg.mesh().filename().empty()) + { + ignerr << "Mesh geometry missing filename" << std::endl; + return geom; + } + rendering::MeshDescriptor descriptor; + + // Assume absolute path to mesh file + descriptor.meshName = _msg.mesh().filename(); + + ignition::common::MeshManager* meshManager = + ignition::common::MeshManager::Instance(); + descriptor.mesh = meshManager->Load(descriptor.meshName); + geom = this->scene->CreateMesh(descriptor); + + scale = msgs::Convert(_msg.mesh().scale()); + } + else + { + ignerr << "Unsupported geometry type" << std::endl; + } + _scale = scale; + _localPose = localPose; + return geom; +} + +///////////////////////////////////////////////// +rendering::MaterialPtr SceneManager::LoadMaterial(const msgs::Material &_msg) +{ + rendering::MaterialPtr material = this->scene->CreateMaterial(); + if (_msg.has_ambient()) + { + material->SetAmbient(msgs::Convert(_msg.ambient())); + } + if (_msg.has_diffuse()) + { + material->SetDiffuse(msgs::Convert(_msg.diffuse())); + } + if (_msg.has_specular()) + { + material->SetSpecular(msgs::Convert(_msg.specular())); + } + if (_msg.has_emissive()) + { + material->SetEmissive(msgs::Convert(_msg.emissive())); + } + + return material; +} + +///////////////////////////////////////////////// +rendering::LightPtr SceneManager::LoadLight(const msgs::Light &_msg) +{ + rendering::LightPtr light; + + switch (_msg.type()) + { + case msgs::Light_LightType_POINT: + light = this->scene->CreatePointLight(); + break; + case msgs::Light_LightType_SPOT: + { + light = this->scene->CreateSpotLight(); + rendering::SpotLightPtr spotLight = + std::dynamic_pointer_cast(light); + spotLight->SetInnerAngle(_msg.spot_inner_angle()); + spotLight->SetOuterAngle(_msg.spot_outer_angle()); + spotLight->SetFalloff(_msg.spot_falloff()); + break; + } + case msgs::Light_LightType_DIRECTIONAL: + { + light = this->scene->CreateDirectionalLight(); + rendering::DirectionalLightPtr dirLight = + std::dynamic_pointer_cast(light); + + if (_msg.has_direction()) + dirLight->SetDirection(msgs::Convert(_msg.direction())); + break; + } + default: + ignerr << "Light type not supported" << std::endl; + return light; + } + + if (_msg.has_pose()) + light->SetLocalPose(msgs::Convert(_msg.pose())); + + if (_msg.has_diffuse()) + light->SetDiffuseColor(msgs::Convert(_msg.diffuse())); + + if (_msg.has_specular()) + light->SetSpecularColor(msgs::Convert(_msg.specular())); + + light->SetAttenuationConstant(_msg.attenuation_constant()); + light->SetAttenuationLinear(_msg.attenuation_linear()); + light->SetAttenuationQuadratic(_msg.attenuation_quadratic()); + light->SetAttenuationRange(_msg.range()); + + light->SetCastShadows(_msg.cast_shadows()); + + this->lights[_msg.id()] = light; + return light; +} + +///////////////////////////////////////////////// +void SceneManager::DeleteEntity(const unsigned int _entity) +{ + if (this->visuals.find(_entity) != this->visuals.end()) + { + auto visual = this->visuals[_entity].lock(); + if (visual) + { + this->scene->DestroyVisual(visual, true); + } + this->visuals.erase(_entity); + } + else if (this->lights.find(_entity) != this->lights.end()) + { + auto light = this->lights[_entity].lock(); + if (light) + { + this->scene->DestroyLight(light, true); + } + this->lights.erase(_entity); + } +} + +///////////////////////////////////////////////// +IgnRenderer::IgnRenderer() + : dataPtr(new IgnRendererPrivate) +{ +} + + +///////////////////////////////////////////////// +IgnRenderer::~IgnRenderer() +{ +} + +///////////////////////////////////////////////// +void IgnRenderer::Render() +{ + if (this->textureDirty) + { + this->dataPtr->camera->SetImageWidth(this->textureSize.width()); + this->dataPtr->camera->SetImageHeight(this->textureSize.height()); + this->dataPtr->camera->SetAspectRatio(this->textureSize.width() / + this->textureSize.height()); + // setting the size should cause the render texture to be rebuilt + this->dataPtr->camera->PreRender(); + + // TODO: OpenGL/Metal + // this->textureId = this->dataPtr->camera->RenderTextureGLId(); + void *texturePtr = nullptr; + this->dataPtr->camera->RenderTextureMetalId(&texturePtr); + this->dataPtr->metalTexture = CFBridgingRelease(texturePtr); + + this->textureDirty = false; + } + + // update the scene + this->dataPtr->sceneManager.Update(); + + // view control + this->HandleMouseEvent(); + + // update and render to texture + this->dataPtr->camera->Update(); + + if (ignition::gui::App()) + { + ignition::gui::App()->sendEvent( + ignition::gui::App()->findChild(), + new gui::events::Render()); + } +} + +///////////////////////////////////////////////// +void IgnRenderer::HandleMouseEvent() +{ + std::lock_guard lock(this->dataPtr->mutex); + this->BroadcastHoverPos(); + this->BroadcastLeftClick(); + this->BroadcastRightClick(); + this->BroadcastKeyPress(); + this->BroadcastKeyRelease(); + this->HandleMouseViewControl(); +} + +///////////////////////////////////////////////// +void IgnRenderer::HandleMouseViewControl() +{ + if (!this->dataPtr->mouseDirty) + return; + + this->dataPtr->viewControl.SetCamera(this->dataPtr->camera); + + if (this->dataPtr->mouseEvent.Type() == common::MouseEvent::SCROLL) + { + this->dataPtr->target = + this->ScreenToScene(this->dataPtr->mouseEvent.Pos()); + this->dataPtr->viewControl.SetTarget(this->dataPtr->target); + double distance = this->dataPtr->camera->WorldPosition().Distance( + this->dataPtr->target); + double amount = -this->dataPtr->drag.Y() * distance / 5.0; + this->dataPtr->viewControl.Zoom(amount); + } + else + { + if (this->dataPtr->drag == math::Vector2d::Zero) + { + this->dataPtr->target = this->ScreenToScene( + this->dataPtr->mouseEvent.PressPos()); + this->dataPtr->viewControl.SetTarget(this->dataPtr->target); + } + + // Pan with left button + if (this->dataPtr->mouseEvent.Buttons() & common::MouseEvent::LEFT) + { + if (Qt::ShiftModifier == QGuiApplication::queryKeyboardModifiers()) + this->dataPtr->viewControl.Orbit(this->dataPtr->drag); + else + this->dataPtr->viewControl.Pan(this->dataPtr->drag); + } + // Orbit with middle button + else if (this->dataPtr->mouseEvent.Buttons() & common::MouseEvent::MIDDLE) + { + this->dataPtr->viewControl.Orbit(this->dataPtr->drag); + } + else if (this->dataPtr->mouseEvent.Buttons() & common::MouseEvent::RIGHT) + { + double hfov = this->dataPtr->camera->HFOV().Radian(); + double vfov = 2.0f * atan(tan(hfov / 2.0f) / + this->dataPtr->camera->AspectRatio()); + double distance = this->dataPtr->camera->WorldPosition().Distance( + this->dataPtr->target); + double amount = ((-this->dataPtr->drag.Y() / + static_cast(this->dataPtr->camera->ImageHeight())) + * distance * tan(vfov/2.0) * 6.0); + this->dataPtr->viewControl.Zoom(amount); + } + } + this->dataPtr->drag = 0; + this->dataPtr->mouseDirty = false; +} + +//////////////////////////////////////////////// +void IgnRenderer::HandleKeyPress(QKeyEvent *_e) +{ + if (_e->isAutoRepeat()) + return; + + std::lock_guard lock(this->dataPtr->mutex); + + this->dataPtr->keyEvent.SetKey(_e->key()); + this->dataPtr->keyEvent.SetText(_e->text().toStdString()); + + this->dataPtr->keyEvent.SetControl( + (_e->modifiers() & Qt::ControlModifier)); + this->dataPtr->keyEvent.SetShift( + (_e->modifiers() & Qt::ShiftModifier)); + this->dataPtr->keyEvent.SetAlt( + (_e->modifiers() & Qt::AltModifier)); + + this->dataPtr->mouseEvent.SetControl(this->dataPtr->keyEvent.Control()); + this->dataPtr->mouseEvent.SetShift(this->dataPtr->keyEvent.Shift()); + this->dataPtr->mouseEvent.SetAlt(this->dataPtr->keyEvent.Alt()); + this->dataPtr->keyEvent.SetType(common::KeyEvent::PRESS); +} + +//////////////////////////////////////////////// +void IgnRenderer::HandleKeyRelease(QKeyEvent *_e) +{ + if (_e->isAutoRepeat()) + return; + + std::lock_guard lock(this->dataPtr->mutex); + + this->dataPtr->keyEvent.SetKey(_e->key()); + + this->dataPtr->keyEvent.SetControl( + (_e->modifiers() & Qt::ControlModifier) + && (_e->key() != Qt::Key_Control)); + this->dataPtr->keyEvent.SetShift( + (_e->modifiers() & Qt::ShiftModifier) + && (_e->key() != Qt::Key_Shift)); + this->dataPtr->keyEvent.SetAlt( + (_e->modifiers() & Qt::AltModifier) + && (_e->key() != Qt::Key_Alt)); + + this->dataPtr->mouseEvent.SetControl(this->dataPtr->keyEvent.Control()); + this->dataPtr->mouseEvent.SetShift(this->dataPtr->keyEvent.Shift()); + this->dataPtr->mouseEvent.SetAlt(this->dataPtr->keyEvent.Alt()); + this->dataPtr->keyEvent.SetType(common::KeyEvent::RELEASE); +} + +///////////////////////////////////////////////// +void IgnRenderer::BroadcastHoverPos() +{ + if (!this->dataPtr->hoverDirty) + return; + + auto pos = this->ScreenToScene(this->dataPtr->mouseHoverPos); + + events::HoverToScene hoverToSceneEvent(pos); + App()->sendEvent(App()->findChild(), &hoverToSceneEvent); +} + +///////////////////////////////////////////////// +void IgnRenderer::BroadcastLeftClick() +{ + if (!this->dataPtr->mouseDirty) + return; + + if (this->dataPtr->mouseEvent.Dragging()) + return; + + if (this->dataPtr->mouseEvent.Button() != common::MouseEvent::LEFT || + this->dataPtr->mouseEvent.Type() != common::MouseEvent::RELEASE) + return; + + auto pos = this->ScreenToScene(this->dataPtr->mouseEvent.Pos()); + + events::LeftClickToScene leftClickToSceneEvent(pos); + events::LeftClickOnScene leftClickOnSceneEvent(this->dataPtr->mouseEvent); + + App()->sendEvent(App()->findChild(), &leftClickToSceneEvent); + App()->sendEvent(App()->findChild(), &leftClickOnSceneEvent); +} + +///////////////////////////////////////////////// +void IgnRenderer::BroadcastRightClick() +{ + if (!this->dataPtr->mouseDirty) + return; + + if (this->dataPtr->mouseEvent.Dragging()) + return; + + if (this->dataPtr->mouseEvent.Button() != common::MouseEvent::RIGHT || + this->dataPtr->mouseEvent.Type() != common::MouseEvent::RELEASE) + return; + + auto pos = this->ScreenToScene(this->dataPtr->mouseEvent.Pos()); + + events::RightClickToScene rightClickToSceneEvent(pos); + events::RightClickOnScene rightClickOnSceneEvent(this->dataPtr->mouseEvent); + + App()->sendEvent(App()->findChild(), &rightClickToSceneEvent); + App()->sendEvent(App()->findChild(), &rightClickOnSceneEvent); +} + +///////////////////////////////////////////////// +void IgnRenderer::BroadcastKeyRelease() +{ + if (this->dataPtr->keyEvent.Type() == common::KeyEvent::RELEASE) + { + events::KeyReleaseOnScene keyRelease(this->dataPtr->keyEvent); + App()->sendEvent(App()->findChild(), &keyRelease); + this->dataPtr->keyEvent.SetType(common::KeyEvent::NO_EVENT); + } +} + +///////////////////////////////////////////////// +void IgnRenderer::BroadcastKeyPress() +{ + if (this->dataPtr->keyEvent.Type() == common::KeyEvent::PRESS) + { + events::KeyPressOnScene keyPress(this->dataPtr->keyEvent); + App()->sendEvent(App()->findChild(), &keyPress); + this->dataPtr->keyEvent.SetType(common::KeyEvent::NO_EVENT); + } +} + +///////////////////////////////////////////////// +void IgnRenderer::Initialize() +{ + // no-op +} + +///////////////////////////////////////////////// +void IgnRenderer::InitialiseOnMainThread() +{ + if (this->initialized) + return; + + // TODO: OpenGL/Metal + std::map params; + //params["useCurrentGLContext"] = "1"; + params["metal"] = "1"; + params["winID"] = std::to_string( + ignition::gui::App()->findChild()-> + QuickWindow()->winId()); + + auto engine = rendering::engine(this->engineName, params); + if (!engine) + { + ignerr << "Engine [" << this->engineName << "] is not supported" + << std::endl; + return; + } + + // Scene + auto scene = engine->SceneByName(this->sceneName); + if (!scene) + { + igndbg << "Create scene [" << this->sceneName << "]" << std::endl; + scene = engine->CreateScene(this->sceneName); + // scene->SetAmbientLight(this->ambientLight); + // scene->SetBackgroundColor(this->backgroundColor); + BuildScene(scene); + } + + auto root = scene->RootVisual(); + + // Camera + this->dataPtr->camera = scene->CreateCamera(); + root->AddChild(this->dataPtr->camera); + this->dataPtr->camera->SetLocalPose(this->cameraPose); + this->dataPtr->camera->SetImageWidth(this->textureSize.width()); + this->dataPtr->camera->SetImageHeight(this->textureSize.height()); + this->dataPtr->camera->SetAntiAliasing(8); + this->dataPtr->camera->SetHFOV(M_PI * 0.5); + // setting the size and calling PreRender should cause the render texture to + // be rebuilt + this->dataPtr->camera->PreRender(); + // TODO: OpenGL/Metal + //this->textureId = this->dataPtr->camera->RenderTextureGLId(); + void *texturePtr = nullptr; + this->dataPtr->camera->RenderTextureMetalId(&texturePtr); + this->dataPtr->metalTexture = CFBridgingRelease(texturePtr); + + // Make service call to populate scene + if (!this->sceneService.empty()) + { + this->dataPtr->sceneManager.Load(this->sceneService, this->poseTopic, + this->deletionTopic, this->sceneTopic, + scene); + this->dataPtr->sceneManager.Request(); + } + + // Ray Query + this->dataPtr->rayQuery = this->dataPtr->camera->Scene()->CreateRayQuery(); + + this->initialized = true; +} + +///////////////////////////////////////////////// +void IgnRenderer::Destroy() +{ + auto engine = rendering::engine(this->engineName); + if (!engine) + return; + auto scene = engine->SceneByName(this->sceneName); + if (!scene) + return; + scene->DestroySensor(this->dataPtr->camera); + + // If that was the last sensor, destroy scene + if (scene->SensorCount() == 0) + { + igndbg << "Destroy scene [" << scene->Name() << "]" << std::endl; + engine->DestroyScene(scene); + + // TODO(anyone) If that was the last scene, terminate engine? + } +} + +///////////////////////////////////////////////// +void IgnRenderer::NewHoverEvent(const math::Vector2i &_hoverPos) +{ + std::lock_guard lock(this->dataPtr->mutex); + this->dataPtr->mouseHoverPos = _hoverPos; + this->dataPtr->hoverDirty = true; +} + +///////////////////////////////////////////////// +void IgnRenderer::NewMouseEvent(const common::MouseEvent &_e, + const math::Vector2d &_drag) +{ + std::lock_guard lock(this->dataPtr->mutex); + this->dataPtr->mouseEvent = _e; + this->dataPtr->drag += _drag; + this->dataPtr->mouseDirty = true; +} + +///////////////////////////////////////////////// +math::Vector3d IgnRenderer::ScreenToScene( + const math::Vector2i &_screenPos) const +{ + // Normalize point on the image + double width = this->dataPtr->camera->ImageWidth(); + double height = this->dataPtr->camera->ImageHeight(); + + double nx = 2.0 * _screenPos.X() / width - 1.0; + double ny = 1.0 - 2.0 * _screenPos.Y() / height; + + // Make a ray query + this->dataPtr->rayQuery->SetFromCamera( + this->dataPtr->camera, math::Vector2d(nx, ny)); + + auto result = this->dataPtr->rayQuery->ClosestPoint(); + if (result) + return result.point; + + // Set point to be 10m away if no intersection found + return this->dataPtr->rayQuery->Origin() + + this->dataPtr->rayQuery->Direction() * 10; +} + +///////////////////////////////////////////////// +void IgnRenderer::MTLTexture(void* _texturePtr /*[out]*/) +{ + *static_cast(_texturePtr) = (void*)CFBridgingRetain(this->dataPtr->metalTexture); +} + +///////////////////////////////////////////////// +RenderThread::RenderThread() +{ + RenderWindowItemPrivate::threads << this; +} + +///////////////////////////////////////////////// +void RenderThread::RenderNext() +{ + // TODO: OpenGL/Metal + //this->context->makeCurrent(this->surface); + + if (!this->ignRenderer.initialized) + { + // Initialize renderer + this->ignRenderer.Initialize(); + } + + // check if engine has been successfully initialized + if (!this->ignRenderer.initialized) + { + ignerr << "Unable to initialize renderer" << std::endl; + return; + } + + this->ignRenderer.Render(); + + // TODO: OpenGL/Metal + //emit TextureReady(this->ignRenderer.textureId, this->ignRenderer.textureSize); + void *texturePtr; + this->ignRenderer.MTLTexture(&texturePtr); + emit TextureReady(texturePtr, this->ignRenderer.textureSize); +} + +///////////////////////////////////////////////// +void RenderThread::ShutDown() +{ + // TODO: OpenGL/Metal + //this->context->makeCurrent(this->surface); + + this->ignRenderer.Destroy(); + + // TODO: OpenGL/Metal + // this->context->doneCurrent(); + // delete this->context; + + // TODO: OpenGL/Metal + // schedule this to be deleted only after we're done cleaning up + //this->surface->deleteLater(); + + // Stop event processing, move the thread to GUI and make sure it is deleted. + this->moveToThread(QGuiApplication::instance()->thread()); +} + + +///////////////////////////////////////////////// +void RenderThread::SizeChanged() +{ + auto item = qobject_cast(this->sender()); + if (!item) + { + ignerr << "Internal error, sender is not QQuickItem." << std::endl; + return; + } + + if (item->width() <= 0 || item->height() <= 0) + return; + + this->ignRenderer.textureSize = QSize(item->width(), item->height()); + this->ignRenderer.textureDirty = true; +} + +///////////////////////////////////////////////// +void RenderThread::InitialiseOnMainThread() +{ + this->ignRenderer.InitialiseOnMainThread(); +} + +///////////////////////////////////////////////// +// TODO: OpenGL/Metal +class ignition::gui::plugins::TextureNodePrivate +{ + public: id metalTexture = nil; + public: id newMetalTexture = nil; +}; + +///////////////////////////////////////////////// +// TODO: OpenGL/Metal +TextureNode::TextureNode(QQuickWindow *_window) + : window(_window), dataPtr(std::make_unique()) +{ + // Our texture node must have a texture, so use the default 0 texture. +//#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) +// this->texture = this->window->createTextureFromId(0, QSize(1, 1)); +//#else +// void * nativeLayout; + this->texture = this->window->createTextureFromNativeObject( + QQuickWindow::NativeObjectTexture, (void*)&this->dataPtr->metalTexture, 0, QSize(1, 1), + QQuickWindow::TextureIsOpaque); +//#endif + this->setTexture(this->texture); +} + +///////////////////////////////////////////////// +TextureNode::~TextureNode() +{ + delete this->texture; +} + +///////////////////////////////////////////////// +// TODO: OpenGL/Metal +void TextureNode::NewTexture(void* _texturePtr, const QSize &_size) +{ + this->mutex.lock(); + // this->id = _id; + this->dataPtr->metalTexture = CFBridgingRelease(_texturePtr); + this->size = _size; + this->mutex.unlock(); + + // We cannot call QQuickWindow::update directly here, as this is only allowed + // from the rendering thread or GUI thread. + emit PendingNewTexture(); +} + +///////////////////////////////////////////////// +// TODO: OpenGL/Metal +void TextureNode::PrepareNode() +{ + this->mutex.lock(); + // int newId = this->id; + this->dataPtr->newMetalTexture = this->dataPtr->metalTexture; + QSize sz = this->size; + //this->id = 0; + this->dataPtr->metalTexture = nil; + this->mutex.unlock(); + if (this->dataPtr->newMetalTexture) + { + delete this->texture; + // note: include QQuickWindow::TextureHasAlphaChannel if the rendered + // content has alpha. +// #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) +// this->texture = this->window->createTextureFromId( +// (void*)&this->dataPtr->newId, sz, QQuickWindow::TextureIsOpaque); +// #else + // TODO(anyone) Use createTextureFromNativeObject + // https://github.com/ignitionrobotics/ign-gui/issues/113 + this->texture = this->window->createTextureFromNativeObject( + QQuickWindow::NativeObjectTexture, (void*)&this->dataPtr->newMetalTexture, 0, sz); + +// #ifndef _WIN32 +// # pragma GCC diagnostic push +// # pragma GCC diagnostic ignored "-Wdeprecated-declarations" +// #endif +// this->texture = this->window->createTextureFromId( +// newId, sz, QQuickWindow::TextureIsOpaque); +// #ifndef _WIN32 +// # pragma GCC diagnostic pop +// #endif +// +// #endif + this->setTexture(this->texture); + + this->markDirty(DirtyMaterial); + + // This will notify the rendering thread that the texture is now being + // rendered and it can start rendering to the other one. + emit TextureInUse(); + } +} + +///////////////////////////////////////////////// +RenderWindowItem::RenderWindowItem(QQuickItem *_parent) + : QQuickItem(_parent), dataPtr(new RenderWindowItemPrivate) +{ + this->setAcceptedMouseButtons(Qt::AllButtons); + this->setFlag(ItemHasContents); + this->dataPtr->renderThread = new RenderThread(); +} + +///////////////////////////////////////////////// +RenderWindowItem::~RenderWindowItem() +{ +} + +///////////////////////////////////////////////// +void RenderWindowItem::Ready() +{ + // TODO: OpenGL/Metal + // carry out any initialisation before moving to thread + this->dataPtr->renderThread->InitialiseOnMainThread(); + + // TODO: OpenGL/Metal + // this->dataPtr->renderThread->surface = new QOffscreenSurface(); + // this->dataPtr->renderThread->surface->setFormat( + // this->dataPtr->renderThread->context->format()); + // this->dataPtr->renderThread->surface->create(); + + this->dataPtr->renderThread->ignRenderer.textureSize = + QSize(std::max({this->width(), 1.0}), std::max({this->height(), 1.0})); + + // Move to Render thread + this->dataPtr->renderThread->moveToThread(this->dataPtr->renderThread); + + this->connect(this, &QObject::destroyed, + this->dataPtr->renderThread, &RenderThread::ShutDown, + Qt::QueuedConnection); + + this->connect(this, &QQuickItem::widthChanged, + this->dataPtr->renderThread, &RenderThread::SizeChanged); + this->connect(this, &QQuickItem::heightChanged, + this->dataPtr->renderThread, &RenderThread::SizeChanged); + + // Running on Render thread + this->dataPtr->renderThread->start(); + this->update(); +} + +///////////////////////////////////////////////// +QSGNode *RenderWindowItem::updatePaintNode(QSGNode *_node, + QQuickItem::UpdatePaintNodeData * /*_data*/) +{ + TextureNode *node = static_cast(_node); + + // TODO: OpenGL/Metal +#if 0 + if (!this->dataPtr->renderThread->context) + { + QOpenGLContext *current = this->window()->openglContext(); + // Some GL implementations require that the currently bound context is + // made non-current before we set up sharing, so we doneCurrent here + // and makeCurrent down below while setting up our own context. + current->doneCurrent(); + + this->dataPtr->renderThread->context = new QOpenGLContext(); + this->dataPtr->renderThread->context->setFormat(current->format()); + this->dataPtr->renderThread->context->setShareContext(current); + this->dataPtr->renderThread->context->create(); + // this->dataPtr->renderThread->context->moveToThread( + // this->dataPtr->renderThread); + + current->makeCurrent(this->window()); + + QMetaObject::invokeMethod(this, "Ready"); + return nullptr; + } +#endif + + // TODO: OpenGL/Metal + if (!this->dataPtr->renderThread->initialised) + { + this->dataPtr->renderThread->initialised = true; + + // Force initialisation on main thread + //QMetaObject::invokeMethod(this, "Ready"); + this->Ready(); + return nullptr; + } + + if (!node) + { + node = new TextureNode(this->window()); + + // Set up connections to get the production of render texture in sync with + // vsync on the rendering thread. + // + // When a new texture is ready on the rendering thread, we use a direct + // connection to the texture node to let it know a new texture can be used. + // The node will then emit PendingNewTexture which we bind to + // QQuickWindow::update to schedule a redraw. + // + // When the scene graph starts rendering the next frame, the PrepareNode() + // function is used to update the node with the new texture. Once it + // completes, it emits TextureInUse() which we connect to the rendering + // thread's RenderNext() to have it start producing content into its render + // texture. + // + // This rendering pipeline is throttled by vsync on the scene graph + // rendering thread. + + this->connect(this->dataPtr->renderThread, &RenderThread::TextureReady, + node, &TextureNode::NewTexture, Qt::DirectConnection); + this->connect(node, &TextureNode::PendingNewTexture, this->window(), + &QQuickWindow::update, Qt::QueuedConnection); + this->connect(this->window(), &QQuickWindow::beforeRendering, node, + &TextureNode::PrepareNode, Qt::DirectConnection); + this->connect(node, &TextureNode::TextureInUse, this->dataPtr->renderThread, + &RenderThread::RenderNext, Qt::QueuedConnection); + + // init ogre before moving to render thread + this->dataPtr->renderThread->RenderNext(); + + // TODO: OpenGL/Metal + // this->dataPtr->renderThread->context->moveToThread( + // this->dataPtr->renderThread); + // this->dataPtr->renderThread->start(); + + // Get the production of FBO textures started.. + QMetaObject::invokeMethod(this->dataPtr->renderThread, "RenderNext", + Qt::QueuedConnection); + } + + node->setRect(this->boundingRect()); + + return node; +} + +///////////////////////////////////////////////// +void RenderWindowItem::SetBackgroundColor(const math::Color &_color) +{ + this->dataPtr->renderThread->ignRenderer.backgroundColor = _color; +} + +///////////////////////////////////////////////// +void RenderWindowItem::SetAmbientLight(const math::Color &_ambient) +{ + this->dataPtr->renderThread->ignRenderer.ambientLight = _ambient; +} + +///////////////////////////////////////////////// +void RenderWindowItem::SetEngineName(const std::string &_name) +{ + this->dataPtr->renderThread->ignRenderer.engineName = _name; +} + +///////////////////////////////////////////////// +void RenderWindowItem::SetSceneName(const std::string &_name) +{ + this->dataPtr->renderThread->ignRenderer.sceneName = _name; +} + +///////////////////////////////////////////////// +void RenderWindowItem::SetCameraPose(const math::Pose3d &_pose) +{ + this->dataPtr->renderThread->ignRenderer.cameraPose = _pose; +} + +///////////////////////////////////////////////// +void RenderWindowItem::SetSceneService(const std::string &_service) +{ + this->dataPtr->renderThread->ignRenderer.sceneService = _service; +} + +///////////////////////////////////////////////// +void RenderWindowItem::SetPoseTopic(const std::string &_topic) +{ + this->dataPtr->renderThread->ignRenderer.poseTopic = _topic; +} + +///////////////////////////////////////////////// +void RenderWindowItem::SetDeletionTopic(const std::string &_topic) +{ + this->dataPtr->renderThread->ignRenderer.deletionTopic = _topic; +} + +///////////////////////////////////////////////// +void RenderWindowItem::SetSceneTopic(const std::string &_topic) +{ + this->dataPtr->renderThread->ignRenderer.sceneTopic = _topic; +} + +///////////////////////////////////////////////// +Scene3D::Scene3D() + : Plugin(), dataPtr(new Scene3DPrivate) +{ + ignwarn << "This plugin is deprecated on ign-gui v6 and will be removed on " + << "ign-gui v7. Use MinimalScene + TransportSceneManager instead." + << std::endl; + + qmlRegisterType("RenderWindow", 1, 0, "RenderWindow"); +} + + +///////////////////////////////////////////////// +Scene3D::~Scene3D() +{ +} + +///////////////////////////////////////////////// +void Scene3D::LoadConfig(const tinyxml2::XMLElement *_pluginElem) +{ + RenderWindowItem *renderWindow = + this->PluginItem()->findChild(); + if (!renderWindow) + { + ignerr << "Unable to find Render Window item. " + << "Render window will not be created" << std::endl; + return; + } + + if (this->title.empty()) + this->title = "3D Scene"; + + // Custom parameters + if (_pluginElem) + { + auto elem = _pluginElem->FirstChildElement("engine"); + if (nullptr != elem && nullptr != elem->GetText()) + { + renderWindow->SetEngineName(elem->GetText()); + // there is a problem with displaying ogre2 render textures that are in + // sRGB format. Workaround for now is to apply gamma correction manually. + // There maybe a better way to solve the problem by making OpenGL calls.. + if (elem->GetText() == std::string("ogre2")) + this->PluginItem()->setProperty("gammaCorrect", true); + } + + elem = _pluginElem->FirstChildElement("scene"); + if (nullptr != elem && nullptr != elem->GetText()) + renderWindow->SetSceneName(elem->GetText()); + + elem = _pluginElem->FirstChildElement("ambient_light"); + if (nullptr != elem && nullptr != elem->GetText()) + { + math::Color ambient; + std::stringstream colorStr; + colorStr << std::string(elem->GetText()); + colorStr >> ambient; + renderWindow->SetAmbientLight(ambient); + } + + elem = _pluginElem->FirstChildElement("background_color"); + if (nullptr != elem && nullptr != elem->GetText()) + { + math::Color bgColor; + std::stringstream colorStr; + colorStr << std::string(elem->GetText()); + colorStr >> bgColor; + renderWindow->SetBackgroundColor(bgColor); + } + + elem = _pluginElem->FirstChildElement("camera_pose"); + if (nullptr != elem && nullptr != elem->GetText()) + { + math::Pose3d pose; + std::stringstream poseStr; + poseStr << std::string(elem->GetText()); + poseStr >> pose; + renderWindow->SetCameraPose(pose); + } + + elem = _pluginElem->FirstChildElement("service"); + if (nullptr != elem && nullptr != elem->GetText()) + { + std::string service = elem->GetText(); + renderWindow->SetSceneService(service); + } + + elem = _pluginElem->FirstChildElement("pose_topic"); + if (nullptr != elem && nullptr != elem->GetText()) + { + std::string topic = elem->GetText(); + renderWindow->SetPoseTopic(topic); + } + + elem = _pluginElem->FirstChildElement("deletion_topic"); + if (nullptr != elem && nullptr != elem->GetText()) + { + std::string topic = elem->GetText(); + renderWindow->SetDeletionTopic(topic); + } + + elem = _pluginElem->FirstChildElement("scene_topic"); + if (nullptr != elem && nullptr != elem->GetText()) + { + std::string topic = elem->GetText(); + renderWindow->SetSceneTopic(topic); + } + } +} + +///////////////////////////////////////////////// +void RenderWindowItem::OnHovered(const ignition::math::Vector2i &_hoverPos) +{ + this->dataPtr->renderThread->ignRenderer.NewHoverEvent(_hoverPos); +} + +///////////////////////////////////////////////// +void RenderWindowItem::mousePressEvent(QMouseEvent *_e) +{ + auto event = convert(*_e); + event.SetPressPos(event.Pos()); + this->dataPtr->mouseEvent = event; + + this->dataPtr->renderThread->ignRenderer.NewMouseEvent( + this->dataPtr->mouseEvent); +} + +//////////////////////////////////////////////// +void RenderWindowItem::mouseReleaseEvent(QMouseEvent *_e) +{ + this->dataPtr->mouseEvent = convert(*_e); + + this->dataPtr->renderThread->ignRenderer.NewMouseEvent( + this->dataPtr->mouseEvent); +} + +//////////////////////////////////////////////// +void RenderWindowItem::mouseMoveEvent(QMouseEvent *_e) +{ + auto event = convert(*_e); + event.SetPressPos(this->dataPtr->mouseEvent.PressPos()); + + if (!event.Dragging()) + return; + + auto dragInt = event.Pos() - this->dataPtr->mouseEvent.Pos(); + auto dragDistance = math::Vector2d(dragInt.X(), dragInt.Y()); + + this->dataPtr->renderThread->ignRenderer.NewMouseEvent(event, dragDistance); + this->dataPtr->mouseEvent = event; +} + +//////////////////////////////////////////////// +void RenderWindowItem::wheelEvent(QWheelEvent *_e) +{ + this->dataPtr->mouseEvent.SetType(common::MouseEvent::SCROLL); +#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) + this->dataPtr->mouseEvent.SetPos(_e->x(), _e->y()); +#else + this->dataPtr->mouseEvent.SetPos(_e->position().x(), _e->position().y()); +#endif + double scroll = (_e->angleDelta().y() > 0) ? -1.0 : 1.0; + this->dataPtr->renderThread->ignRenderer.NewMouseEvent( + this->dataPtr->mouseEvent, math::Vector2d(scroll, scroll)); +} + +//////////////////////////////////////////////// +void RenderWindowItem::keyPressEvent(QKeyEvent *_event) +{ + this->HandleKeyPress(_event); +} + +//////////////////////////////////////////////// +void RenderWindowItem::keyReleaseEvent(QKeyEvent *_event) +{ + this->HandleKeyRelease(_event); +} + +//////////////////////////////////////////////// +void RenderWindowItem::HandleKeyPress(QKeyEvent *_e) +{ + this->dataPtr->renderThread->ignRenderer.HandleKeyPress(_e); +} + +//////////////////////////////////////////////// +void RenderWindowItem::HandleKeyRelease(QKeyEvent *_e) +{ + this->dataPtr->renderThread->ignRenderer.HandleKeyRelease(_e); +} + +///////////////////////////////////////////////// +bool Scene3D::eventFilter(QObject *_obj, QEvent *_event) +{ + if (_event->type() == QEvent::KeyPress) + { + QKeyEvent *keyEvent = static_cast(_event); + if (keyEvent) + { + auto renderWindow = this->PluginItem()->findChild(); + renderWindow->HandleKeyPress(keyEvent); + } + } + else if (_event->type() == QEvent::KeyRelease) + { + QKeyEvent *keyEvent = static_cast(_event); + if (keyEvent) + { + auto renderWindow = this->PluginItem()->findChild(); + renderWindow->HandleKeyRelease(keyEvent); + } + } + + // Standard event processing + return QObject::eventFilter(_obj, _event); +} + +///////////////////////////////////////////////// +void Scene3D::OnHovered(int _mouseX, int _mouseY) +{ + auto renderWindow = this->PluginItem()->findChild(); + renderWindow->OnHovered({_mouseX, _mouseY}); +} + +///////////////////////////////////////////////// +void Scene3D::OnFocusWindow() +{ + auto renderWindow = this->PluginItem()->findChild(); + renderWindow->forceActiveFocus(); +} + +// Register this plugin +IGNITION_ADD_PLUGIN(ignition::gui::plugins::Scene3D, + ignition::gui::Plugin) diff --git a/test/integration/scene3d.cc b/test/integration/scene3d.cc index 4a7d79afa..6d9005493 100644 --- a/test/integration/scene3d.cc +++ b/test/integration/scene3d.cc @@ -79,7 +79,7 @@ TEST(Scene3DTest, IGN_UTILS_TEST_ENABLED_ONLY_ON_LINUX(Config)) // Load plugin const char *pluginStr = "" - "ogre" + "ogre2" "banana" "1.0 0 0" "0 1 0" @@ -152,7 +152,7 @@ TEST(Scene3DTest, IGN_UTILS_TEST_ENABLED_ONLY_ON_LINUX(Events)) // Load plugin const char *pluginStr = "" - "ogre" + "ogre2" "banana" "1.0 0 0" "0 1 0"