From 63b3599679be48d6fee513a433dae00a97763e0d Mon Sep 17 00:00:00 2001 From: Rhys Mainwaring Date: Fri, 4 Mar 2022 19:32:22 +0000 Subject: [PATCH] Add Metal support to MinimalScene and Qt Application (#323) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Metal] Add Metal support to MinimalScene and Qt Application Allow MinimalScene to use either OpenGL or Metal render systems Add classes to abstract out render hardware interfaces: - IgnCameraTextureRhi - RenderThreadRhi - TextureNodeRhi Add implementations for OpenGL and Metal in: - MinimalSceneRhiOpenGL - MinimalSceneRhiMetal - Modify the existing MinimalScene class to forward rendering calls to the (virtual) render hardware interface functions. - Modify plugin CMakeLists.txt to conditionally compile Metal support if the platform is macOS - Update Application.cc to support either OpenGL or Metal Refactor MinimalScene to use GraphicAPI - Use the GraphicsAPI enum to set the choice of graphics interface (OpenGL or Metal). - Change the MinimalScene plugin element to ... Add documentation and confirm to code style - Document the OpenGL and Metal render interface classes - Revert default engine to ogre Set graphics API to Metal for macOS, otherwise use OpenGL - Update Application.cc to only use Metal for macOS - Fix issues with code check Signed-off-by: Rhys Mainwaring Co-authored-by: Ian Chen Co-authored-by: Alejandro Hernández Cordero --- src/Application.cc | 23 ++ src/plugins/minimal_scene/CMakeLists.txt | 33 +- src/plugins/minimal_scene/MinimalScene.cc | 332 ++++++++++++------ src/plugins/minimal_scene/MinimalScene.hh | 68 +++- src/plugins/minimal_scene/MinimalSceneRhi.cc | 55 +++ src/plugins/minimal_scene/MinimalSceneRhi.hh | 128 +++++++ .../minimal_scene/MinimalSceneRhiMetal.hh | 135 +++++++ .../minimal_scene/MinimalSceneRhiMetal.mm | 223 ++++++++++++ .../minimal_scene/MinimalSceneRhiOpenGL.cc | 281 +++++++++++++++ .../minimal_scene/MinimalSceneRhiOpenGL.hh | 147 ++++++++ 10 files changed, 1305 insertions(+), 120 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 diff --git a/src/Application.cc b/src/Application.cc index 872d3758c..10324d4f1 100644 --- a/src/Application.cc +++ b/src/Application.cc @@ -89,6 +89,29 @@ Application::Application(int &_argc, char **_argv, const WindowType _type) { igndbg << "Initializing application." << std::endl; +#if __APPLE__ + // Use the Metal graphics API on macOS. + igndbg << "Qt using Metal graphics interface" << std::endl; + QQuickWindow::setSceneGraphBackend(QSGRendererInterface::MetalRhi); + + // TODO(srmainwaring): implement facility for overriding the default + // graphics API in macOS, in which case there are restrictions on + // the version of OpenGL used. + // + // macOS only supports OpenGL <= 4.1. + // This must be called before the main window is 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); +#else + // Otherwise use OpenGL + igndbg << "Qt using OpenGL graphics interface" << std::endl; +#endif + // Configure console common::Console::SetPrefix("[GUI] "); 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 f34a818b1..62c525219 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 rhiParams; + + /// \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,20 @@ class ignition::gui::plugins::RenderWindowItem::Implementation /// \brief Keep latest mouse event public: common::MouseEvent mouseEvent; + /// \brief True if initialized + public: bool initialized = false; + + /// \brief True if initializing (started but not complete) + public: bool initializing = false; + + /// \brief Graphics API. The default is platform specific. + public: ignition::rendering::GraphicsAPI graphicsAPI = +#ifdef __APPLE__ + rendering::GraphicsAPI::METAL; +#else + rendering::GraphicsAPI::OPENGL; +#endif + /// \brief Render thread public: RenderThread *renderThread = nullptr; @@ -239,7 +262,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 +292,8 @@ void RenderSync::Shutdown() IgnRenderer::IgnRenderer() : dataPtr(utils::MakeUniqueImpl()) { + // Set default graphics API to OpenGL + this->SetGraphicsAPI(rendering::GraphicsAPI::OPENGL); } ///////////////////////////////////////////////// @@ -300,7 +324,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(); @@ -514,12 +539,11 @@ void IgnRenderer::Initialize() if (this->initialized) return; - std::map params; - params["useCurrentGLContext"] = "1"; - params["winID"] = std::to_string( + this->dataPtr->rhiParams["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->rhiParams); if (!engine) { ignerr << "Engine [" << this->engineName << "] is not supported" @@ -558,7 +582,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(); @@ -566,6 +592,28 @@ void IgnRenderer::Initialize() this->initialized = true; } +///////////////////////////////////////////////// +void IgnRenderer::SetGraphicsAPI(const rendering::GraphicsAPI &_graphicsAPI) +{ + // Create render interface and reset params + this->dataPtr->rhiParams.clear(); + + if (_graphicsAPI == rendering::GraphicsAPI::OPENGL) + { + qDebug().nospace() << "Creating ign-rendering interface for OpenGL"; + this->dataPtr->rhiParams["useCurrentGLContext"] = "1"; + this->dataPtr->rhi = std::make_unique(); + } +#ifdef __APPLE__ + else if (_graphicsAPI == rendering::GraphicsAPI::METAL) + { + qDebug().nospace() << "Creating ign-renderering interface for Metal"; + this->dataPtr->rhiParams["metal"] = "1"; + this->dataPtr->rhi = std::make_unique(); + } +#endif +} + ///////////////////////////////////////////////// void IgnRenderer::Destroy() { @@ -642,9 +690,18 @@ math::Vector3d IgnRenderer::ScreenToScene( this->dataPtr->rayQuery->Direction() * 10; } +///////////////////////////////////////////////// +void IgnRenderer::TextureId(void* _texturePtr) +{ + this->dataPtr->rhi->TextureId(_texturePtr); +} + ///////////////////////////////////////////////// RenderThread::RenderThread() { + // Set default graphics API to OpenGL + this->SetGraphicsAPI(rendering::GraphicsAPI::OPENGL); + RenderWindowItem::Implementation::threads << this; qRegisterMetaType("RenderSync*"); } @@ -652,38 +709,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(); @@ -708,34 +744,86 @@ 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::SetGraphicsAPI(const rendering::GraphicsAPI &_graphicsAPI) +{ + // Set the graphics API for the IgnRenderer + this->ignRenderer.SetGraphicsAPI(_graphicsAPI); + + // Create the render interface + if (_graphicsAPI == rendering::GraphicsAPI::OPENGL) + { + qDebug().nospace() << "Creating render thread interface for OpenGL"; + this->rhi = std::make_unique(&this->ignRenderer); + } +#ifdef __APPLE__ + else if (_graphicsAPI == rendering::GraphicsAPI::METAL) + { + qDebug().nospace() << "Creating render thread interface for Metal"; + this->rhi = std::make_unique(&this->ignRenderer); + } #endif - this->setTexture(this->texture); } ///////////////////////////////////////////////// -TextureNode::~TextureNode() +void RenderThread::Initialize() { - delete this->texture; + this->rhi->Initialize(); } ///////////////////////////////////////////////// -void TextureNode::NewTexture(uint _id, const QSize &_size) +TextureNode::TextureNode( + QQuickWindow *_window, + RenderSync &_renderSync, + const rendering::GraphicsAPI &_graphicsAPI) + : renderSync(_renderSync) , window(_window) { - this->mutex.lock(); - this->id = _id; - this->size = _size; - this->mutex.unlock(); + if (_graphicsAPI == rendering::GraphicsAPI::OPENGL) + { + qDebug().nospace() << "Creating texture node render interface for OpenGL"; + this->rhi = std::make_unique(_window); + } +#ifdef __APPLE__ + else if (_graphicsAPI == rendering::GraphicsAPI::METAL) + { + 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->rhi->NewTexture(_texturePtr, _size); // We cannot call QQuickWindow::update directly here, as this is only allowed // from the rendering thread or GUI thread. @@ -745,34 +833,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); @@ -830,24 +895,40 @@ RenderWindowItem::~RenderWindowItem() } ///////////////////////////////////////////////// +// This slot will run on the main thread 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->graphicsAPI == rendering::GraphicsAPI::OPENGL) + { + this->dataPtr->renderThread->SetSurface(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})); + // Carry out initialization on main thread before moving to render thread + this->dataPtr->renderThread->Initialize(); + + if (this->dataPtr->graphicsAPI == rendering::GraphicsAPI::OPENGL) + { + // Move context to the render thread + this->dataPtr->renderThread->Context()->moveToThread( + this->dataPtr->renderThread); + } this->dataPtr->renderThread->moveToThread(this->dataPtr->renderThread); + this->dataPtr->renderThread->ignRenderer.textureSize = + QSize(std::max({this->width(), 1.0}), std::max({this->height(), 1.0})); + this->connect(this, &QQuickItem::widthChanged, this->dataPtr->renderThread, &RenderThread::SizeChanged); this->connect(this, &QQuickItem::heightChanged, this->dataPtr->renderThread, &RenderThread::SizeChanged); this->dataPtr->renderThread->start(); + this->dataPtr->initializing = false; + this->dataPtr->initialized = true; this->update(); } @@ -857,30 +938,60 @@ 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); + // Exit immediately if still initializing + if (this->dataPtr->initializing) + { + return nullptr; + } + this->dataPtr->initializing = true; + + // Set the render thread's render system + this->dataPtr->renderThread->SetGraphicsAPI( + this->dataPtr->graphicsAPI); + + if (this->dataPtr->graphicsAPI == rendering::GraphicsAPI::OPENGL) + { + 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(); + + // The slot "Ready" runs on the main thread, move the context to match + this->dataPtr->renderThread->Context()->moveToThread( + QApplication::instance()->thread()); - current->makeCurrent(this->window()); + current->makeCurrent(this->window()); - QMetaObject::invokeMethod(this, "Ready"); + // Initialize on main thread + QMetaObject::invokeMethod(this, "Ready", Qt::QueuedConnection); + } + else if (this->dataPtr->graphicsAPI == rendering::GraphicsAPI::METAL) + { + // Initialize on main thread + QMetaObject::invokeMethod(this, "Ready", Qt::QueuedConnection); + } + else + { + ignerr << "GraphicsAPI [" + << rendering::GraphicsAPIUtils::Str(this->dataPtr->graphicsAPI) + << "] is not supported" + << std::endl; + } return nullptr; } if (!node) { - node = new TextureNode(this->window(), this->dataPtr->renderSync); + node = new TextureNode(this->window(), this->dataPtr->renderSync, + this->dataPtr->graphicsAPI); // Set up connections to get the production of render texture in sync with // vsync on the rendering thread. @@ -995,6 +1106,14 @@ void RenderWindowItem::SetSkyEnabled(const bool &_sky) this->dataPtr->renderThread->ignRenderer.skyEnable = _sky; } +///////////////////////////////////////////////// +void RenderWindowItem::SetGraphicsAPI( + const rendering::GraphicsAPI &_graphicsAPI) +{ + this->dataPtr->graphicsAPI = _graphicsAPI; + this->dataPtr->renderThread->SetGraphicsAPI(_graphicsAPI); +} + ///////////////////////////////////////////////// MinimalScene::MinimalScene() : Plugin(), dataPtr(utils::MakeUniqueImpl()) @@ -1138,7 +1257,16 @@ void MinimalScene::LoadConfig(const tinyxml2::XMLElement *_pluginElem) { renderWindow->SetSkyEnabled(true); if (!elem->NoChildren()) - ignwarn << "Child elements of are not supported yet" << std::endl; + ignwarn << "Child elements of are not supported yet" + << std::endl; + } + + elem = _pluginElem->FirstChildElement("graphics_api"); + if (nullptr != elem && nullptr != elem->GetText()) + { + rendering::GraphicsAPI graphicsAPI = + rendering::GraphicsAPIUtils::Set(elem->GetText()); + renderWindow->SetGraphicsAPI(graphicsAPI); } } diff --git a/src/plugins/minimal_scene/MinimalScene.hh b/src/plugins/minimal_scene/MinimalScene.hh index 49b72198b..60c1f2a20 100644 --- a/src/plugins/minimal_scene/MinimalScene.hh +++ b/src/plugins/minimal_scene/MinimalScene.hh @@ -27,9 +27,12 @@ #include #include #include +#include #include "ignition/gui/Plugin.hh" +#include "MinimalSceneRhi.hh" + namespace ignition { namespace gui @@ -56,6 +59,8 @@ namespace plugins /// * \ : Camera's near clipping plane distance, defaults to 0.01 /// * \ : Camera's far clipping plane distance, defaults to 1000.0 /// * \ : If present, sky is enabled. + /// * \ : Optional graphics API name. Valid choices are: + /// 'opengl', 'metal'. Defaults to 'opengl'. class MinimalScene : public Plugin { Q_OBJECT @@ -105,9 +110,14 @@ namespace plugins /// synchronize Qt and worker thread (this) public: void Render(RenderSync *_renderSync); - /// \brief Initialize the render engine + /// \brief Initialize the render engine and scene. + /// On macOS this must be called on the main thread. public: void Initialize(); + /// \brief Set the graphics API + /// \param[in] _graphicsAPI The type of graphics API + public: void SetGraphicsAPI(const rendering::GraphicsAPI &_graphicsAPI); + /// \brief Destroy camera associated with this renderer public: void Destroy(); @@ -173,7 +183,8 @@ namespace plugins /// Values is constantly constantly cycled/swapped/changed /// from a worker thread /// Don't read this directly - public: GLuint textureId; + /// \param[out] _texturePtr Pointer to a texture Id + public: void TextureId(void* _texturePtr); /// \brief Render engine to use public: std::string engineName = "ogre"; @@ -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 @@ -251,18 +262,37 @@ namespace plugins /// \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(uint _id, const QSize &_size); + signals: void TextureReady(void* _texturePtr, 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] _context OpenGL context + public: void SetContext(QOpenGLContext *_context); + + /// \brief Set the graphics API + /// \param[in] _graphicsAPI The type of graphics API + public: void SetGraphicsAPI(const rendering::GraphicsAPI &_graphicsAPI); + + /// \brief Carry out initialisation. + /// On macOS this must be run on the main thread + public: void Initialize(); /// \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 +372,10 @@ namespace plugins /// \param[in] _sky True to enable the sky, false otherwise. public: void SetSkyEnabled(const bool &_sky); + /// \brief Set the graphics API + /// \param[in] _graphicsAPI The type of graphics API + public: void SetGraphicsAPI(const rendering::GraphicsAPI& _graphicsAPI); + /// \brief Slot called when thread is ready to be started public Q_SLOTS: void Ready(); @@ -394,17 +428,20 @@ namespace plugins /// \param[in] _window Window to display the texture /// \param[in] _renderSync RenderSync to safely /// synchronize Qt (this) and worker thread + /// \param[in] _graphicsAPI The type of graphics API public: explicit TextureNode(QQuickWindow *_window, - RenderSync &_renderSync); + RenderSync &_renderSync, + const rendering::GraphicsAPI &_graphicsAPI); /// \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..1fa64a41f --- /dev/null +++ b/src/plugins/minimal_scene/MinimalSceneRhi.cc @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2022 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 reinterpret_cast(0); +} + +///////////////////////////////////////////////// +void RenderThreadRhi::SetSurface(QOffscreenSurface *) //NOLINT +{ + /* no-op */ +} + +///////////////////////////////////////////////// +QOpenGLContext *RenderThreadRhi::Context() const +{ + return reinterpret_cast(0); +} + +///////////////////////////////////////////////// +void RenderThreadRhi::SetContext(QOpenGLContext *) //NOLINT +{ + /* 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..908b53355 --- /dev/null +++ b/src/plugins/minimal_scene/MinimalSceneRhi.hh @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2022 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 +{ + /// \brief Render interface class to handle OpenGL / Metal compatibility + /// of camera textures in IgnRenderer + // + /// Each supported graphics API must implement this interface + /// to provide access to the underlying render system's texture. + class IgnCameraTextureRhi + { + /// \brief Destructor + public: virtual ~IgnCameraTextureRhi(); + + /// \brief Update the texture for a camera + /// \param[in] _camera Pointer to the camera providing the texture + public: virtual void Update(rendering::CameraPtr _camera) = 0; + + /// \brief Get the graphics API texture Id + /// \param[out] _texturePtr Pointer to a texture Id + public: virtual void TextureId(void* _texturePtr) = 0; + }; + + /// \brief Ign-rendering renderer. + class IgnRenderer; + class RenderSync; + + /// \brief Render interface class to handle OpenGL / Metal compatibility + /// in RenderThread + class RenderThreadRhi + { + /// \brief Destructor + public: virtual ~RenderThreadRhi(); + + /// \brief Offscreen surface to render to + public: virtual QOffscreenSurface *Surface() const; + + /// \brief Set the offscreen surface to render to + // + /// \param[in] _surface Off-screen surface format + public: virtual void SetSurface(QOffscreenSurface *_surface); + + /// \brief OpenGL context to be passed to the render engine + public: virtual QOpenGLContext *Context() const; + + /// \brief Set the OpenGL context to be passed to the render engine + // + /// \param[in] _context OpenGL context + public: virtual void SetContext(QOpenGLContext *_context); + + /// \brief Carry out initialization + // + /// On macOS this must be run on the main thread + public: virtual void Initialize() = 0; + + /// \brief Render when safe + /// \param[in] _renderSync RenderSync to safely + /// synchronize Qt and worker thread (this) + public: virtual void RenderNext(RenderSync *_renderSync) = 0; + + /// \brief Return a pointer to the graphics API texture Id + public: virtual void* TexturePtr() const = 0; + + /// \brief Return the size of the texture + public: virtual QSize TextureSize() const = 0; + + /// \brief Shutdown the thread and the render engine + public: virtual void ShutDown() = 0; + }; + + /// \brief Render interface class to handle OpenGL / Metal compatibility + /// in TextureNode + class TextureNodeRhi + { + /// \brief Destructor + public: virtual ~TextureNodeRhi(); + + /// \brief Get the Qt scene graph texture + public: virtual QSGTexture *Texture() const = 0; + + /// \brief Return true if a new texture has been received + /// from the render thread + public: virtual bool HasNewTexture() const = 0; + + /// \brief This function gets called on the render thread and will + /// store the texture id and size and schedule an update on the window. + /// \param[in] _texturePtr Pointer to a texture Id + /// \param[in] _size Texture size + public: virtual void NewTexture( + void* _texturePtr, const QSize &_size) = 0; + + /// \brief Before the scene graph starts to render, we update to the + /// pending texture + 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..772aad6da --- /dev/null +++ b/src/plugins/minimal_scene/MinimalSceneRhiMetal.hh @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2022 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 +{ + /// \brief Private data for IgnCameraTextureRhiMetal + class IgnCameraTextureRhiMetalPrivate; + + /// \brief Implementation of IgnCameraTextureRhi for the Metal graphics API + class IgnCameraTextureRhiMetal : public IgnCameraTextureRhi + { + // Documentation inherited + public: virtual ~IgnCameraTextureRhiMetal() override; + + /// \brief Constructor + public: IgnCameraTextureRhiMetal(); + + // Documentation inherited + public: virtual void Update(rendering::CameraPtr _camera) override; + + // Documentation inherited + public: virtual void TextureId(void* _texturePtr) override; + + /// \internal Pointer to private data + private: std::unique_ptr dataPtr; + }; + + /// \brief Private data for RenderThreadRhiMetal + class RenderThreadRhiMetalPrivate; + + /// \brief Implementation of RenderThreadRhi for the Metal graphics API + class RenderThreadRhiMetal : public RenderThreadRhi + { + // Documentation inherited + public: virtual ~RenderThreadRhiMetal() override; + + /// \brief Constructor + /// \param[in] _renderer The Ign-rendering renderer + public: RenderThreadRhiMetal(IgnRenderer *_renderer); + + // Documentation inherited + public: virtual void Initialize() override; + + // Documentation inherited + public: virtual void RenderNext(RenderSync *_renderSync) override; + + // Documentation inherited + public: virtual void* TexturePtr() const override; + + // Documentation inherited + public: virtual QSize TextureSize() const override; + + // Documentation inherited + public: virtual void ShutDown() override; + + /// \internal Prevent copy and assignment + private: RenderThreadRhiMetal( + const RenderThreadRhiMetal &_other) = delete; + private: RenderThreadRhiMetal& operator=( + const RenderThreadRhiMetal &_other) = delete; + + /// \internal Pointer to private data + private: std::unique_ptr dataPtr; + }; + + /// \brief Private data for TextureNodeRhiMetal + class TextureNodeRhiMetalPrivate; + + /// \brief Implementation of TextureNodeRhi for the Metal graphics API + class TextureNodeRhiMetal : public TextureNodeRhi + { + // Documentation inherited + public: virtual ~TextureNodeRhiMetal() override; + + /// \brief Constructor + /// \param[in] _window Window to display the texture + public: TextureNodeRhiMetal(QQuickWindow *_window); + + // Documentation inherited + public: virtual QSGTexture *Texture() const override; + + // Documentation inherited + public: virtual bool HasNewTexture() const override; + + // Documentation inherited + public: virtual void NewTexture( + void* _texturePtr, const QSize &_size)override; + + // Documentation inherited + public: virtual void PrepareNode() override; + + /// \internal Prevent copy and assignment + private: TextureNodeRhiMetal( + const TextureNodeRhiMetal &_other) = delete; + private: TextureNodeRhiMetal& operator=( + const TextureNodeRhiMetal &_other) = delete; + + /// \internal Pointer to private data + 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..a7f753659 --- /dev/null +++ b/src/plugins/minimal_scene/MinimalSceneRhiMetal.mm @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2022 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::Initialize() +{ + this->dataPtr->renderer->Initialize(); +} + +///////////////////////////////////////////////// +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..f955a88af --- /dev/null +++ b/src/plugins/minimal_scene/MinimalSceneRhiOpenGL.cc @@ -0,0 +1,281 @@ +/* + * Copyright (C) 2022 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) +{ + *reinterpret_cast(_texturePtr) = (void*)&this->dataPtr->textureId; //NOLINT +} + +///////////////////////////////////////////////// +///////////////////////////////////////////////// +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::Initialize() +{ + this->dataPtr->context->makeCurrent(this->dataPtr->surface); + + this->dataPtr->renderer->Initialize(); + + 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, 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..57b95bd52 --- /dev/null +++ b/src/plugins/minimal_scene/MinimalSceneRhiOpenGL.hh @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2022 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_MINIMALSCENERHIOPENGL_HH_ +#define IGNITION_GUI_PLUGINS_MINIMALSCENE_MINIMALSCENERHIOPENGL_HH_ + +#include "MinimalSceneRhi.hh" +#include "ignition/gui/Plugin.hh" + +#include +#include +#include + +#include + +namespace ignition +{ +namespace gui +{ +namespace plugins +{ + /// \brief Private data for IgnCameraTextureRhiOpenGL + class IgnCameraTextureRhiOpenGLPrivate; + + /// \brief Implementation of IgnCameraTextureRhi for the OpenGL graphics API + class IgnCameraTextureRhiOpenGL : public IgnCameraTextureRhi + { + // Documentation inherited + public: virtual ~IgnCameraTextureRhiOpenGL() override; + + /// \brief Constructor + public: IgnCameraTextureRhiOpenGL(); + + // Documentation inherited + public: virtual void Update(rendering::CameraPtr _camera) override; + + // Documentation inherited + public: virtual void TextureId(void* _texturePtr) override; + + /// \internal Pointer to private data + private: std::unique_ptr dataPtr; + }; + + /// \brief Private data for RenderThreadRhiOpenGLPrivate + class RenderThreadRhiOpenGLPrivate; + + /// \brief Implementation of RenderThreadRhi for the OpenGL graphics API + class RenderThreadRhiOpenGL : public RenderThreadRhi + { + // Documentation inherited + public: virtual ~RenderThreadRhiOpenGL() override; + + /// \brief Constructor + /// \param[in] _renderer The Ign-rendering renderer + public: RenderThreadRhiOpenGL(IgnRenderer *_renderer); + + // Documentation inherited + public: virtual QOffscreenSurface *Surface() const override; + + // Documentation inherited + public: virtual void SetSurface(QOffscreenSurface *_surface) override; + + // Documentation inherited + public: virtual QOpenGLContext *Context() const override; + + // Documentation inherited + public: virtual void SetContext(QOpenGLContext *_context) override; + + // Documentation inherited + public: virtual void Initialize() override; + + // Documentation inherited + public: virtual void RenderNext(RenderSync *_renderSync) override; + + // Documentation inherited + public: virtual void* TexturePtr() const override; + + // Documentation inherited + public: virtual QSize TextureSize() const override; + + // Documentation inherited + public: virtual void ShutDown() override; + + /// \internal Prevent copy and assignment + private: RenderThreadRhiOpenGL( + const RenderThreadRhiOpenGL &_other) = delete; + private: RenderThreadRhiOpenGL& operator=( + const RenderThreadRhiOpenGL &_other) = delete; + + /// \internal Pointer to private data + private: std::unique_ptr dataPtr; + }; + + /// \brief Private data for TextureNodeRhiOpenGL + class TextureNodeRhiOpenGLPrivate; + + /// \brief Implementation of TextureNodeRhi for the OpenGL graphics API + class TextureNodeRhiOpenGL : public TextureNodeRhi + { + // Documentation inherited + public: virtual ~TextureNodeRhiOpenGL() override; + + /// \brief Constructor + /// \param[in] _window Window to display the texture + public: TextureNodeRhiOpenGL(QQuickWindow *_window); + + // Documentation inherited + public: virtual QSGTexture *Texture() const override; + + // Documentation inherited + public: virtual bool HasNewTexture() const override; + + // Documentation inherited + public: virtual void NewTexture( + void* _texturePtr, const QSize &_size) override; + + // Documentation inherited + public: virtual void PrepareNode() override; + + /// \internal Prevent copy and assignment + private: TextureNodeRhiOpenGL( + const TextureNodeRhiOpenGL &_other) = delete; + private: TextureNodeRhiOpenGL& operator=( + const TextureNodeRhiOpenGL &_other) = delete; + + /// \internal Pointer to private data + private: std::unique_ptr dataPtr; + }; +} +} +} + +#endif