From 6a708b1f1870ed54e7ad0575e372e4754a73bc03 Mon Sep 17 00:00:00 2001 From: Alexander Bock Date: Thu, 3 Mar 2022 23:47:09 +0100 Subject: [PATCH] Implement new Spout input methods to Tileproviders and new Renderables (#1901) * Implement new Spout input methods to Tileproviders and new Renderables Co-authored-by: Marco Silva --- apps/OpenSpace/ext/sgct | 2 +- apps/OpenSpace/main.cpp | 116 ++-- config/single_gui_spout.json | 67 ++ config/spout_output.json | 36 - config/spout_output_cubemap.json | 60 ++ config/spout_output_equirectangular.json | 60 ++ config/spout_output_fisheye.json | 60 ++ config/spout_output_flat.json | 59 ++ .../colorlayers/terra_texture_spout.asset | 18 + modules/base/rendering/renderablesphere.cpp | 9 +- modules/base/rendering/renderablesphere.h | 4 + modules/globebrowsing/CMakeLists.txt | 2 + modules/globebrowsing/globebrowsingmodule.cpp | 4 + modules/globebrowsing/include.cmake | 5 + .../shaders/texturetilemapping.glsl | 3 +- modules/globebrowsing/src/gpulayergroup.cpp | 2 + modules/globebrowsing/src/layer.cpp | 5 +- modules/globebrowsing/src/layergroupid.h | 8 +- .../src/tileprovider/defaulttileprovider.cpp | 5 + .../src/tileprovider/defaulttileprovider.h | 1 + .../imagesequencetileprovider.cpp | 4 + .../tileprovider/imagesequencetileprovider.h | 1 + .../tileprovider/singleimagetileprovider.cpp | 4 + .../tileprovider/singleimagetileprovider.h | 1 + .../sizereferencetileprovider.cpp | 4 + .../tileprovider/sizereferencetileprovider.h | 1 + .../src/tileprovider/spoutimageprovider.cpp | 219 +++++++ .../src/tileprovider/spoutimageprovider.h | 66 ++ .../src/tileprovider/temporaltileprovider.cpp | 8 + .../src/tileprovider/temporaltileprovider.h | 2 + .../tileprovider/tileindextileprovider.cpp | 4 + .../src/tileprovider/tileindextileprovider.h | 1 + .../src/tileprovider/tileprovider.cpp | 3 +- .../src/tileprovider/tileprovider.h | 6 + .../src/tileprovider/tileproviderbyindex.cpp | 4 + .../src/tileprovider/tileproviderbyindex.h | 1 + .../src/tileprovider/tileproviderbylevel.cpp | 4 + .../src/tileprovider/tileproviderbylevel.h | 1 + modules/spout/CMakeLists.txt | 4 + modules/spout/ext/spout/SpoutLibrary.h | 2 + modules/spout/renderableplanespout.cpp | 114 +--- modules/spout/renderableplanespout.h | 16 +- modules/spout/renderablespherespout.cpp | 120 ++++ modules/spout/renderablespherespout.h | 58 ++ modules/spout/screenspacespout.cpp | 118 +--- modules/spout/screenspacespout.h | 19 +- modules/spout/spoutmodule.cpp | 2 + modules/spout/spoutwrapper.cpp | 613 ++++++++++++++++++ modules/spout/spoutwrapper.h | 190 ++++++ openspace.cfg | 43 +- 50 files changed, 1792 insertions(+), 367 deletions(-) create mode 100644 config/single_gui_spout.json delete mode 100644 config/spout_output.json create mode 100644 config/spout_output_cubemap.json create mode 100644 config/spout_output_equirectangular.json create mode 100644 config/spout_output_fisheye.json create mode 100644 config/spout_output_flat.json create mode 100644 data/assets/scene/solarsystem/planets/earth/layers/colorlayers/terra_texture_spout.asset create mode 100644 modules/globebrowsing/src/tileprovider/spoutimageprovider.cpp create mode 100644 modules/globebrowsing/src/tileprovider/spoutimageprovider.h create mode 100644 modules/spout/renderablespherespout.cpp create mode 100644 modules/spout/renderablespherespout.h create mode 100644 modules/spout/spoutwrapper.cpp create mode 100644 modules/spout/spoutwrapper.h diff --git a/apps/OpenSpace/ext/sgct b/apps/OpenSpace/ext/sgct index 3befe6c7a0..c5288e6f1d 160000 --- a/apps/OpenSpace/ext/sgct +++ b/apps/OpenSpace/ext/sgct @@ -1 +1 @@ -Subproject commit 3befe6c7a083bf11a71eab8e87101afce8049997 +Subproject commit c5288e6f1da32366b0b35e2072cd0c21eb1f7453 diff --git a/apps/OpenSpace/main.cpp b/apps/OpenSpace/main.cpp index 2b0f28996e..5857440162 100644 --- a/apps/OpenSpace/main.cpp +++ b/apps/OpenSpace/main.cpp @@ -79,7 +79,7 @@ #endif // OPENVR_SUPPORT #ifdef OPENSPACE_HAS_SPOUT -#include "SpoutLibrary.h" +#include #endif // OPENSPACE_HAS_SPOUT #ifdef OPENSPACE_HAS_NVTOOLS @@ -120,16 +120,11 @@ Window* FirstOpenVRWindow = nullptr; * the \c leftOrMain and \c right members respectively. */ struct SpoutWindow { - struct SpoutData { - SPOUTHANDLE handle = nullptr; - bool initialized = false; - }; - /// The left framebuffer (or main, if there is no stereo rendering) - SpoutData leftOrMain; + openspace::spout::SpoutSender leftOrMain; /// The right framebuffer - SpoutData right; + openspace::spout::SpoutSender right; /// The window ID of this windows size_t windowId = size_t(-1); @@ -291,41 +286,33 @@ void mainInitFunc(GLFWwindow*) { #ifdef OPENSPACE_HAS_SPOUT SpoutWindow w; - - w.windowId = i; + bool retValue = true; + std::string mainWindowName = window.name(); const Window::StereoMode sm = window.stereoMode(); const bool hasStereo = (sm != Window::StereoMode::NoStereo) && (sm < Window::StereoMode::SideBySide); if (hasStereo) { - SpoutWindow::SpoutData& left = w.leftOrMain; - left.handle = GetSpout(); - left.initialized = left.handle->CreateSender( - (window.name() + "_left").c_str(), - window.framebufferResolution().x, - window.framebufferResolution().y - ); - - SpoutWindow::SpoutData& right = w.right; - right.handle = GetSpout(); - right.initialized = right.handle->CreateSender( - (window.name() + "_right").c_str(), - window.framebufferResolution().x, - window.framebufferResolution().y - ); - } - else { - SpoutWindow::SpoutData& main = w.leftOrMain; - main.handle = GetSpout(); - main.initialized = main.handle->CreateSender( - window.name().c_str(), + mainWindowName = window.name() + "_left"; + retValue &= w.right.updateSenderName((window.name() + "_right").c_str()); + retValue &= w.right.updateSenderSize( window.framebufferResolution().x, window.framebufferResolution().y ); } - SpoutWindows.push_back(std::move(w)); + retValue &= w.leftOrMain.updateSenderName(mainWindowName.c_str()); + retValue &= w.leftOrMain.updateSenderSize( + window.framebufferResolution().x, + window.framebufferResolution().y + ); + + w.windowId = i; + + if (retValue) { + SpoutWindows.push_back(std::move(w)); + } #else LWARNING("Spout was requested, but program was compiled without Spout support"); #endif // OPENSPACE_HAS_SPOUT @@ -513,6 +500,29 @@ void mainRenderFunc(const sgct::RenderData& data) { currentModelMatrix = modelMatrix; currentModelViewProjectionMatrix = modelMatrix * viewMatrix * projectionMatrix; global::openSpaceEngine->render(modelMatrix, viewMatrix, projectionMatrix); + +#ifdef OPENSPACE_HAS_SPOUT + for (SpoutWindow& w : SpoutWindows) { + sgct::Window& window = *Engine::instance().windows()[w.windowId]; + int width = window.framebufferResolution().x; + int height = window.framebufferResolution().y; + + w.leftOrMain.saveGLState(); + + if (w.leftOrMain.isCreated() && w.leftOrMain.updateSenderSize(width, height)) + { + GLuint texId = window.frameBufferTexture(Window::TextureIndex::LeftEye); + w.leftOrMain.updateSender(texId, static_cast(GL_TEXTURE_2D)); + } + + if (w.right.isCreated() && w.right.updateSenderSize(width, height)) { + GLuint texId = window.frameBufferTexture(Window::TextureIndex::RightEye); + w.right.updateSender(texId, static_cast(GL_TEXTURE_2D)); + } + + w.leftOrMain.restoreGLState(); + } +#endif // OPENSPACE_HAS_SPOUT } catch (const ghoul::RuntimeError& e) { LERRORC(e.component, e.message); @@ -564,34 +574,6 @@ void mainPostDrawFunc() { global::openSpaceEngine->postDraw(); -#ifdef OPENSPACE_HAS_SPOUT - for (const SpoutWindow& w : SpoutWindows) { - sgct::Window& window = *Engine::instance().windows()[w.windowId]; - if (w.leftOrMain.initialized) { - const GLuint texId = window.frameBufferTexture(Window::TextureIndex::LeftEye); - glBindTexture(GL_TEXTURE_2D, texId); - w.leftOrMain.handle->SendTexture( - texId, - GLuint(GL_TEXTURE_2D), - window.framebufferResolution().x, - window.framebufferResolution().y - ); - } - - if (w.right.initialized) { - const GLuint tId = window.frameBufferTexture(Window::TextureIndex::RightEye); - glBindTexture(GL_TEXTURE_2D, tId); - w.right.handle->SendTexture( - tId, - GLuint(GL_TEXTURE_2D), - window.framebufferResolution().x, - window.framebufferResolution().y - ); - } - } - glBindTexture(GL_TEXTURE_2D, 0); -#endif // OPENSPACE_HAS_SPOUT - LTRACE("main::mainPostDrawFunc(end)"); } @@ -797,8 +779,8 @@ void setSgctDelegateFunctions() { Viewport* viewport = currentWindow->viewports().front().get(); if (viewport != nullptr) { if (viewport->hasSubViewports() && viewport->nonLinearProjection()) { - int res = viewport->nonLinearProjection()->cubemapResolution(); - return glm::ivec2(res, res); + ivec2 dim = viewport->nonLinearProjection()->cubemapResolution(); + return glm::ivec2(dim.x, dim.y); } else if (currentWindow->viewports().size() > 1) { // @TODO (abock, 2020-04-09) This should probably be based on the current @@ -1361,14 +1343,8 @@ int main(int argc, char* argv[]) { #ifdef OPENSPACE_HAS_SPOUT for (SpoutWindow& w : SpoutWindows) { - if (w.leftOrMain.handle) { - w.leftOrMain.handle->ReleaseReceiver(); - w.leftOrMain.handle->Release(); - } - if (w.right.handle) { - w.right.handle->ReleaseReceiver(); - w.right.handle->Release(); - } + w.leftOrMain.release(); + w.right.release(); } #endif // OPENSPACE_HAS_SPOUT diff --git a/config/single_gui_spout.json b/config/single_gui_spout.json new file mode 100644 index 0000000000..db17b98a06 --- /dev/null +++ b/config/single_gui_spout.json @@ -0,0 +1,67 @@ +{ + "version": 1, + "masteraddress": "localhost", + "externalcontrolport": 20500, + "settings": { + "display": { + "swapinterval": 0 + } + }, + "nodes": [ + { + "address": "localhost", + "port": 20401, + "windows": [ + { + "name": "OpenSpace", + "pos": { "x": 50, "y": 50 }, + "size": { "x": 1280, "y": 720 }, + "stereo": "none", + "tags": [ "Spout" ], + "viewports": [ + { + "pos": { "x": 0.0, "y": 0.0 }, + "size": { "x": 1.0, "y": 1.0 }, + "tracked": true, + "projection": { + "type": "PlanarProjection", + "fov": { + "hfov": 80.0, + "vfov": 50.53401565551758 + }, + "orientation": { "yaw": 0.0, "pitch": 0.0, "roll": 0.0 } + } + } + ] + }, + { + "name": "GUI", + "pos": { "x": 50, "y": 50 }, + "size": { "x": 1280, "y": 720 }, + "stereo": "none", + "tags": [ "GUI" ], + "viewports": [ + { + "pos": { "x": 0.0, "y": 0.0 }, + "size": { "x": 1.0, "y": 1.0 }, + "projection": { + "type": "PlanarProjection", + "fov": { + "hfov": 80.0, + "vfov": 50.53401565551758 + }, + "orientation": { "yaw": 0.0, "pitch": 0.0, "roll": 0.0 } + } + } + ] + } + ] + } + ], + "users": [ + { + "eyeseparation": 0.06499999761581421, + "pos": { "x": 0.0, "y": 0.0, "z": 0.0 } + } + ] +} diff --git a/config/spout_output.json b/config/spout_output.json deleted file mode 100644 index a6d5d1148d..0000000000 --- a/config/spout_output.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "version": 1, - "masteraddress": "localhost", - "nodes": [ - { - "address": "localhost", - "port": 20401, - "windows": [ - { - "fullscreen": false, - "name": "OpenSpace", - "stereo": "none", - "size": { "x": 1024, "y": 1024 }, - "viewports": [ - { - "pos": { "x": 0.0, "y": 0.0 }, - "size": { "x": 1.0, "y": 1.0 }, - "projection": { - "type": "SpoutOutputProjection", - "quality": "1k", - "mappingspoutname": "OpenSpace", - "background": { "r": 0.1, "g": 0.1, "b": 0.1, "a": 1.0 } - } - } - ] - } - ] - } - ], - "users": [ - { - "eyeseparation": 0.06, - "pos": { "x": 0.0, "y": 0.0, "z": 0.0 } - } - ] -} diff --git a/config/spout_output_cubemap.json b/config/spout_output_cubemap.json new file mode 100644 index 0000000000..064f48ecfc --- /dev/null +++ b/config/spout_output_cubemap.json @@ -0,0 +1,60 @@ +{ + "version": 1, + "masteraddress": "localhost", + "scene": { + "offset": { "x": 0.0, "y": 0.0, "z": 0.0 }, + "orientation": { "yaw": 0.0, "pitch": -90.0, "roll": 0.0 }, + "scale": 1.0 + }, + "settings": { + "display": { + "swapinterval": 1 + } + }, + "nodes": [ + { + "address": "localhost", + "port": 20401, + "windows": [ + { + "name": "OpenSpace", + "size": { "x": 1024, "y": 1024 }, + "stereo": "none", + "viewports": [ + { + "pos": { "x": 0.0, "y": 0.0 }, + "size": { "x": 1.0, "y": 1.0 }, + "projection": { + "type": "SpoutOutputProjection", + "background": { + "r": 0.1, + "g": 0.1, + "b": 0.1, + "a": 1.0 + }, + "channels": { + "bottom": true, + "left": true, + "right": true, + "top": true, + "zleft": true, + "zright": true + }, + "mapping": "cubemap", + "mappingspoutname": "OS_CUBEMAP", + "orientation": { "pitch": 0.0, "roll": 0.0, "yaw": 0.0 }, + "quality": "1024" + } + } + ] + } + ] + } + ], + "users": [ + { + "eyeseparation": 0.05999999865889549, + "pos": { "x": 0.0, "y": 0.0, "z": 0.0 } + } + ] +} diff --git a/config/spout_output_equirectangular.json b/config/spout_output_equirectangular.json new file mode 100644 index 0000000000..87a3bd98c2 --- /dev/null +++ b/config/spout_output_equirectangular.json @@ -0,0 +1,60 @@ +{ + "version": 1, + "masteraddress": "localhost", + "scene": { + "offset": { "x": 0.0, "y": 0.0, "z": 0.0 }, + "orientation": { "yaw": 0.0, "pitch": -90.0, "roll": 0.0 }, + "scale": 1.0 + }, + "settings": { + "display": { + "swapinterval": 1 + } + }, + "nodes": [ + { + "address": "localhost", + "port": 20401, + "windows": [ + { + "name": "OpenSpace", + "size": { "x": 1024, "y": 1024 }, + "stereo": "none", + "viewports": [ + { + "pos": { "x": 0.0, "y": 0.0 }, + "size": { "x": 1.0, "y": 1.0 }, + "projection": { + "type": "SpoutOutputProjection", + "background": { + "r": 0.1, + "g": 0.1, + "b": 0.1, + "a": 1.0 + }, + "channels": { + "bottom": true, + "left": true, + "right": true, + "top": true, + "zleft": true, + "zright": true + }, + "mapping": "equirectangular", + "mappingspoutname": "OS_EQUIRECTANGULAR", + "orientation": { "pitch": 0.0, "roll": 0.0, "yaw": 0.0 }, + "quality": "1024" + } + } + ] + } + ] + } + ], + "users": [ + { + "eyeseparation": 0.06, + "pos": { "x": 0.0, "y": 0.0, "z": 0.0 } + } + ] +} diff --git a/config/spout_output_fisheye.json b/config/spout_output_fisheye.json new file mode 100644 index 0000000000..cd1b2a490e --- /dev/null +++ b/config/spout_output_fisheye.json @@ -0,0 +1,60 @@ +{ + "version": 1, + "masteraddress": "localhost", + "scene": { + "offset": { "x": 0.0, "y": 0.0, "z": 0.0 }, + "orientation": { "yaw": 0.0, "pitch": 0.0, "roll": 0.0 }, + "scale": 1.0 + }, + "settings": { + "display": { + "swapinterval": 1 + } + }, + "nodes": [ + { + "address": "localhost", + "port": 20401, + "windows": [ + { + "name": "OpenSpace", + "size": { "x": 1024, "y": 1024 }, + "stereo": "none", + "viewports": [ + { + "pos": { "x": 0.0, "y": 0.0 }, + "size": { "x": 1.0, "y": 1.0 }, + "projection": { + "type": "SpoutOutputProjection", + "background": { + "r": 0.1, + "g": 0.1, + "b": 0.1, + "a": 1.0 + }, + "channels": { + "bottom": false, + "left": true, + "right": true, + "top": true, + "zleft": true, + "zright": false + }, + "mapping": "fisheye", + "mappingspoutname": "OS_FISHEYE", + "orientation": { "pitch": 0.0, "roll": 0.0, "yaw": 45.0 }, + "quality": "1024" + } + } + ] + } + ] + } + ], + "users": [ + { + "eyeseparation": 0.06, + "pos": { "x": 0.0, "y": 0.0, "z": 0.0 } + } + ] +} diff --git a/config/spout_output_flat.json b/config/spout_output_flat.json new file mode 100644 index 0000000000..0d1b775d77 --- /dev/null +++ b/config/spout_output_flat.json @@ -0,0 +1,59 @@ +{ + "version": 1, + "masteraddress": "localhost", + "scene": { + "offset": { "x": 0.0, "y": 0.0, "z": 0.0 }, + "orientation": { "yaw": 0.0, "pitch": 0.0, "roll": 0.0 }, + "scale": 1.0 + }, + "settings": { + "display": { + "swapinterval": 1 + } + }, + "nodes": [ + { + "address": "localhost", + "port": 20401, + "windows": [ + { + "name": "OpenSpace", + "size": { "x": 1440, "y": 810 }, + "stereo": "none", + "viewports": [ + { + "pos": { "x": 0.0, "y": 0.0 }, + "size": { "x": 1.0, "y": 1.0 }, + "projection": { + "PlanarProjection": { + "fov": { + "hfov": 80.0, + "vfov": 50.534015846724 + }, + "orientation": { "yaw": 0.0, "pitch": 0.0, "roll": 0.0 } + }, + "background": { + "r": 0.1, + "g": 0.1, + "b": 0.1, + "a": 1.0 + }, + "drawMain": true, + "height": "1080", + "mappingspoutname": "OS_FLAT", + "type": "SpoutFlatProjection", + "width": "1920" + } + } + ] + } + ] + } + ], + "users": [ + { + "eyeseparation": 0.05999999865889549, + "pos": { "x": 0.0, "y": 0.0, "z": 0.0 } + } + ] +} diff --git a/data/assets/scene/solarsystem/planets/earth/layers/colorlayers/terra_texture_spout.asset b/data/assets/scene/solarsystem/planets/earth/layers/colorlayers/terra_texture_spout.asset new file mode 100644 index 0000000000..60059cf84d --- /dev/null +++ b/data/assets/scene/solarsystem/planets/earth/layers/colorlayers/terra_texture_spout.asset @@ -0,0 +1,18 @@ +local globeIdentifier = asset.require("../../earth").Earth.Identifier + +local layer = { + Identifier = "TextureSpout", + SpoutName = "SPOUT_TERRA_RECEIVER", + Type = "SpoutImageTileLayer" +} + +asset.onInitialize(function () + openspace.globebrowsing.addLayer(globeIdentifier, "ColorLayers", layer) +end) + + +asset.onDeinitialize(function() + openspace.globebrowsing.deleteLayer(globeIdentifier, "ColorLayers", layer) +end) + +asset.export("layer", layer) diff --git a/modules/base/rendering/renderablesphere.cpp b/modules/base/rendering/renderablesphere.cpp index 54ccf357ed..4324de5a7e 100644 --- a/modules/base/rendering/renderablesphere.cpp +++ b/modules/base/rendering/renderablesphere.cpp @@ -339,7 +339,8 @@ void RenderableSphere::render(const RenderData& data, RendererTasks&) { ghoul::opengl::TextureUnit unit; unit.activate(); - _texture->bind(); + bindTexture(); + defer{ unbindTexture(); }; _shader->setUniform(_uniformCache.colorTexture, unit); // Setting these states should not be necessary, @@ -390,6 +391,12 @@ void RenderableSphere::update(const UpdateData&) { } } +void RenderableSphere::bindTexture() { + _texture->bind(); +} + +void RenderableSphere::unbindTexture() {} + void RenderableSphere::loadTexture() { if (!_texturePath.value().empty()) { std::unique_ptr texture = diff --git a/modules/base/rendering/renderablesphere.h b/modules/base/rendering/renderablesphere.h index 5934dbdfdb..d06320de0c 100644 --- a/modules/base/rendering/renderablesphere.h +++ b/modules/base/rendering/renderablesphere.h @@ -60,6 +60,10 @@ public: static documentation::Documentation Documentation(); +protected: + virtual void bindTexture(); + virtual void unbindTexture(); + private: void loadTexture(); diff --git a/modules/globebrowsing/CMakeLists.txt b/modules/globebrowsing/CMakeLists.txt index 26d8a1e221..868a47ea2e 100644 --- a/modules/globebrowsing/CMakeLists.txt +++ b/modules/globebrowsing/CMakeLists.txt @@ -63,6 +63,7 @@ set(HEADER_FILES src/tileprovider/imagesequencetileprovider.h src/tileprovider/singleimagetileprovider.h src/tileprovider/sizereferencetileprovider.h + src/tileprovider/spoutimageprovider.h src/tileprovider/temporaltileprovider.h src/tileprovider/texttileprovider.h src/tileprovider/tileindextileprovider.h @@ -102,6 +103,7 @@ set(SOURCE_FILES src/tileprovider/imagesequencetileprovider.cpp src/tileprovider/singleimagetileprovider.cpp src/tileprovider/sizereferencetileprovider.cpp + src/tileprovider/spoutimageprovider.cpp src/tileprovider/temporaltileprovider.cpp src/tileprovider/texttileprovider.cpp src/tileprovider/tileindextileprovider.cpp diff --git a/modules/globebrowsing/globebrowsingmodule.cpp b/modules/globebrowsing/globebrowsingmodule.cpp index 8b0438741c..8a39e68755 100644 --- a/modules/globebrowsing/globebrowsingmodule.cpp +++ b/modules/globebrowsing/globebrowsingmodule.cpp @@ -39,6 +39,7 @@ #include #include #include +#include #include #include #include @@ -306,6 +307,9 @@ void GlobeBrowsingModule::internalInitialize(const ghoul::Dictionary& dict) { fTileProvider->registerClass( LAYER_TYPE_NAMES[static_cast(TypeID::ImageSequenceTileLayer)] ); + fTileProvider->registerClass( + LAYER_TYPE_NAMES[static_cast(TypeID::SpoutImageTileLayer)] + ); fTileProvider->registerClass( LAYER_TYPE_NAMES[static_cast(TypeID::TemporalTileLayer)] ); diff --git a/modules/globebrowsing/include.cmake b/modules/globebrowsing/include.cmake index 5667e647fb..2c6ecfb83d 100644 --- a/modules/globebrowsing/include.cmake +++ b/modules/globebrowsing/include.cmake @@ -2,4 +2,9 @@ set (OPENSPACE_DEPENDENCIES debugging ) +# Don't **actually** add it as a dependency. Only mark it as a dependency if it was already enabled anyway. We need to do this as the GlobeBrowsing partially depends on being able to detect if OpenSpace was compiled with Spout support or not +if (OPENSPACE_MODULE_SPOUT) + set(OPENSPACE_DEPENDENCIES ${OPENSPACE_DEPENDENCIES} spout) +endif (OPENSPACE_MODULE_SPOUT) + set (DEFAULT_MODULE ON) diff --git a/modules/globebrowsing/shaders/texturetilemapping.glsl b/modules/globebrowsing/shaders/texturetilemapping.glsl index 76700f17e4..7995291c79 100644 --- a/modules/globebrowsing/shaders/texturetilemapping.glsl +++ b/modules/globebrowsing/shaders/texturetilemapping.glsl @@ -163,7 +163,8 @@ vec4 getSample#{layerGroup}#{i}(vec2 uv, vec3 levelWeights, c = getTexVal(#{layerGroup}[#{i}].pile, levelWeights, uv, #{layerGroup}[#{i}].padding); #elif (#{#{layerGroup}#{i}LayerType} == 8) // SolidColor c.rgb = #{layerGroup}[#{i}].color; - +#elif (#{#{layerGroup}#{i}LayerType} == 9) // SpoutImageTileLayer + c = getTexVal(#{layerGroup}[#{i}].pile, levelWeights, uv, #{layerGroup}[#{i}].padding); #endif return c; diff --git a/modules/globebrowsing/src/gpulayergroup.cpp b/modules/globebrowsing/src/gpulayergroup.cpp index d32cbd9ace..151c029e59 100644 --- a/modules/globebrowsing/src/gpulayergroup.cpp +++ b/modules/globebrowsing/src/gpulayergroup.cpp @@ -68,6 +68,7 @@ void GPULayerGroup::setValue(ghoul::opengl::ProgramObject& program, // Intentional fall through. Same for all tile layers case layergroupid::TypeID::DefaultTileLayer: case layergroupid::TypeID::SingleImageTileLayer: + case layergroupid::TypeID::SpoutImageTileLayer: case layergroupid::TypeID::ImageSequenceTileLayer: case layergroupid::TypeID::SizeReferenceTileLayer: case layergroupid::TypeID::TemporalTileLayer: @@ -149,6 +150,7 @@ void GPULayerGroup::bind(ghoul::opengl::ProgramObject& p, // Intentional fall through. Same for all tile layers case layergroupid::TypeID::DefaultTileLayer: case layergroupid::TypeID::SingleImageTileLayer: + case layergroupid::TypeID::SpoutImageTileLayer: case layergroupid::TypeID::ImageSequenceTileLayer: case layergroupid::TypeID::SizeReferenceTileLayer: case layergroupid::TypeID::TemporalTileLayer: diff --git a/modules/globebrowsing/src/layer.cpp b/modules/globebrowsing/src/layer.cpp index f5b9070074..8ac7fc9aa1 100644 --- a/modules/globebrowsing/src/layer.cpp +++ b/modules/globebrowsing/src/layer.cpp @@ -115,7 +115,7 @@ namespace { std::optional type [[codegen::inlist("DefaultTileLayer", "SingleImageTileLayer", "ImageSequenceTileLayer", "SizeReferenceTileLayer", "TemporalTileLayer", "TileIndexTileLayer", "ByIndexTileLayer", - "ByLevelTileLayer", "SolidColor")]]; + "ByLevelTileLayer", "SolidColor", "SpoutImageTileLayer")]]; // Determine whether the layer is enabled or not. If this value is not specified, // the layer is disabled @@ -302,6 +302,7 @@ Layer::Layer(layergroupid::GroupID id, const ghoul::Dictionary& layerDict, // Intentional fall through. Same for all tile layers case layergroupid::TypeID::DefaultTileLayer: case layergroupid::TypeID::SingleImageTileLayer: + case layergroupid::TypeID::SpoutImageTileLayer: case layergroupid::TypeID::ImageSequenceTileLayer: case layergroupid::TypeID::SizeReferenceTileLayer: case layergroupid::TypeID::TemporalTileLayer: @@ -470,6 +471,7 @@ void Layer::initializeBasedOnType(layergroupid::TypeID id, ghoul::Dictionary ini // Intentional fall through. Same for all tile layers case layergroupid::TypeID::DefaultTileLayer: case layergroupid::TypeID::SingleImageTileLayer: + case layergroupid::TypeID::SpoutImageTileLayer: case layergroupid::TypeID::ImageSequenceTileLayer: case layergroupid::TypeID::SizeReferenceTileLayer: case layergroupid::TypeID::TemporalTileLayer: @@ -502,6 +504,7 @@ void Layer::addVisibleProperties() { // Intentional fall through. Same for all tile layers case layergroupid::TypeID::DefaultTileLayer: case layergroupid::TypeID::SingleImageTileLayer: + case layergroupid::TypeID::SpoutImageTileLayer: case layergroupid::TypeID::ImageSequenceTileLayer: case layergroupid::TypeID::SizeReferenceTileLayer: case layergroupid::TypeID::TemporalTileLayer: diff --git a/modules/globebrowsing/src/layergroupid.h b/modules/globebrowsing/src/layergroupid.h index 9da5cba759..07377fb108 100644 --- a/modules/globebrowsing/src/layergroupid.h +++ b/modules/globebrowsing/src/layergroupid.h @@ -56,7 +56,7 @@ enum GroupID { Unknown }; -static constexpr const int NUM_LAYER_TYPES = 9; +static constexpr const int NUM_LAYER_TYPES = 10; static constexpr const char* LAYER_TYPE_NAMES[NUM_LAYER_TYPES] = { "DefaultTileLayer", "SingleImageTileLayer", @@ -66,7 +66,8 @@ static constexpr const char* LAYER_TYPE_NAMES[NUM_LAYER_TYPES] = { "TileIndexTileLayer", "ByIndexTileLayer", "ByLevelTileLayer", - "SolidColor" + "SolidColor", + "SpoutImageTileLayer" }; /** @@ -82,7 +83,8 @@ enum class TypeID { TileIndexTileLayer = 5, ByIndexTileLayer = 6, ByLevelTileLayer = 7, - SolidColor = 8 + SolidColor = 8, + SpoutImageTileLayer = 9 }; static constexpr int NUM_ADJUSTMENT_TYPES = 3; diff --git a/modules/globebrowsing/src/tileprovider/defaulttileprovider.cpp b/modules/globebrowsing/src/tileprovider/defaulttileprovider.cpp index 6a11f3acb4..c3ec9b5916 100644 --- a/modules/globebrowsing/src/tileprovider/defaulttileprovider.cpp +++ b/modules/globebrowsing/src/tileprovider/defaulttileprovider.cpp @@ -195,6 +195,11 @@ void DefaultTileProvider::reset() { _asyncTextureDataProvider->prepareToBeDeleted(); } +int DefaultTileProvider::minLevel() { + ghoul_assert(_asyncTextureDataProvider, "No data provider"); + return 1; +} + int DefaultTileProvider::maxLevel() { ghoul_assert(_asyncTextureDataProvider, "No data provider"); return _asyncTextureDataProvider->rawTileDataReader().maxChunkLevel(); diff --git a/modules/globebrowsing/src/tileprovider/defaulttileprovider.h b/modules/globebrowsing/src/tileprovider/defaulttileprovider.h index 0cb6195ea8..ea5b8f87f4 100644 --- a/modules/globebrowsing/src/tileprovider/defaulttileprovider.h +++ b/modules/globebrowsing/src/tileprovider/defaulttileprovider.h @@ -41,6 +41,7 @@ public: TileDepthTransform depthTransform() override final; void update() override final; void reset() override final; + int minLevel() override final; int maxLevel() override final; float noDataValueAsFloat() override final; diff --git a/modules/globebrowsing/src/tileprovider/imagesequencetileprovider.cpp b/modules/globebrowsing/src/tileprovider/imagesequencetileprovider.cpp index 597cacda81..b0b1423be5 100644 --- a/modules/globebrowsing/src/tileprovider/imagesequencetileprovider.cpp +++ b/modules/globebrowsing/src/tileprovider/imagesequencetileprovider.cpp @@ -149,6 +149,10 @@ void ImageSequenceTileProvider::reset() { } } +int ImageSequenceTileProvider::minLevel() { + return 1; +} + int ImageSequenceTileProvider::maxLevel() { return _currentTileProvider ? _currentTileProvider->maxLevel() : 0; } diff --git a/modules/globebrowsing/src/tileprovider/imagesequencetileprovider.h b/modules/globebrowsing/src/tileprovider/imagesequencetileprovider.h index afacdfe05b..af461f78de 100644 --- a/modules/globebrowsing/src/tileprovider/imagesequencetileprovider.h +++ b/modules/globebrowsing/src/tileprovider/imagesequencetileprovider.h @@ -40,6 +40,7 @@ public: TileDepthTransform depthTransform() override final; void update() override final; void reset() override final; + int minLevel() override final; int maxLevel() override final; float noDataValueAsFloat() override final; diff --git a/modules/globebrowsing/src/tileprovider/singleimagetileprovider.cpp b/modules/globebrowsing/src/tileprovider/singleimagetileprovider.cpp index a6e6911ef2..69698568d7 100644 --- a/modules/globebrowsing/src/tileprovider/singleimagetileprovider.cpp +++ b/modules/globebrowsing/src/tileprovider/singleimagetileprovider.cpp @@ -93,6 +93,10 @@ void SingleImageProvider::reset() { _tile = Tile{ _tileTexture.get(), std::nullopt, Tile::Status::OK }; } +int SingleImageProvider::minLevel() { + return 1; +} + int SingleImageProvider::maxLevel() { return 1337; // unlimited } diff --git a/modules/globebrowsing/src/tileprovider/singleimagetileprovider.h b/modules/globebrowsing/src/tileprovider/singleimagetileprovider.h index a911c719f3..c1132b5e33 100644 --- a/modules/globebrowsing/src/tileprovider/singleimagetileprovider.h +++ b/modules/globebrowsing/src/tileprovider/singleimagetileprovider.h @@ -40,6 +40,7 @@ public: TileDepthTransform depthTransform() override final; void update() override final; void reset() override final; + int minLevel() override final; int maxLevel() override final; float noDataValueAsFloat() override final; diff --git a/modules/globebrowsing/src/tileprovider/sizereferencetileprovider.cpp b/modules/globebrowsing/src/tileprovider/sizereferencetileprovider.cpp index 6da9209036..bc60464713 100644 --- a/modules/globebrowsing/src/tileprovider/sizereferencetileprovider.cpp +++ b/modules/globebrowsing/src/tileprovider/sizereferencetileprovider.cpp @@ -114,6 +114,10 @@ TileDepthTransform SizeReferenceTileProvider::depthTransform() { void SizeReferenceTileProvider::update() {} +int SizeReferenceTileProvider::minLevel() { + return 1; +} + int SizeReferenceTileProvider::maxLevel() { return 1337; // unlimited } diff --git a/modules/globebrowsing/src/tileprovider/sizereferencetileprovider.h b/modules/globebrowsing/src/tileprovider/sizereferencetileprovider.h index adb25c42c7..770eccd819 100644 --- a/modules/globebrowsing/src/tileprovider/sizereferencetileprovider.h +++ b/modules/globebrowsing/src/tileprovider/sizereferencetileprovider.h @@ -37,6 +37,7 @@ public: Tile::Status tileStatus(const TileIndex& index) override final; TileDepthTransform depthTransform() override final; void update() override final; + int minLevel() override final; int maxLevel() override final; float noDataValueAsFloat() override final; diff --git a/modules/globebrowsing/src/tileprovider/spoutimageprovider.cpp b/modules/globebrowsing/src/tileprovider/spoutimageprovider.cpp new file mode 100644 index 0000000000..31826e9358 --- /dev/null +++ b/modules/globebrowsing/src/tileprovider/spoutimageprovider.cpp @@ -0,0 +1,219 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2022 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#include + +#include + +#ifdef OPENSPACE_HAS_SPOUT +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif // WIN32_LEAN_AND_MEAN +#ifndef NOMINMAX +#define NOMINMAX +#endif // NOMINMAX +#include +#endif + +namespace { + struct [[codegen::Dictionary(SpoutImageProvider)]] Parameters { + std::optional spoutName; + }; +#include "spoutimageprovider_codegen.cpp" +} // namespace + +namespace openspace::globebrowsing { + +documentation::Documentation SpoutImageProvider::Documentation() { + return codegen::doc("globebrowsing_spoutimageprovider"); +} + +SpoutImageProvider::SpoutImageProvider(const ghoul::Dictionary& dictionary) { + ZoneScoped + +#ifdef OPENSPACE_HAS_SPOUT + spoutReceiver = std::make_unique( + *this, + dictionary + ); + + spoutReceiver->onUpdateTexture([this](int width, int height) { + for (int i = 0; i < 2; i++) { + if (!fbo[i]) { + glGenFramebuffers(1, &fbo[i]); + } + tileTexture[i].release(); + tileTexture[i] = std::make_unique( + glm::uvec3(width / 2, height, 1), + GL_TEXTURE_2D, + ghoul::opengl::Texture::Format::RGBA, + GL_RGBA, + GL_UNSIGNED_BYTE, + ghoul::opengl::Texture::FilterMode::Linear, + ghoul::opengl::Texture::WrappingMode::Repeat, + ghoul::opengl::Texture::AllocateData::No, + ghoul::opengl::Texture::TakeOwnership::No + ); + + if (!tileTexture[i]) { + return false; + } + tileTexture[i]->uploadTexture(); + tiles[i] = Tile{ tileTexture[i].get(), std::nullopt, Tile::Status::OK }; + } + return true; + }); + + + spoutReceiver->onReleaseTexture([this]() { + for (int i = 0; i < 2; i++) { + if (fbo[i]) { + glDeleteFramebuffers(1, &fbo[i]); + } + tileTexture[i].release(); + fbo[i] = 0; + } + }); + + + spoutReceiver->onUpdateReceiver([this](int width, int height, unsigned int texture) { + glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo[0]); + glFramebufferTexture2D( + GL_READ_FRAMEBUFFER, + GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, + static_cast(texture), + 0 + ); + glReadBuffer(GL_COLOR_ATTACHMENT0); + + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo[1]); + glFramebufferTexture2D( + GL_DRAW_FRAMEBUFFER, + GL_COLOR_ATTACHMENT1, + GL_TEXTURE_2D, + static_cast(*tileTexture[0]), + 0 + ); + glDrawBuffer(GL_COLOR_ATTACHMENT1); + + glBlitFramebuffer( + width / 2, + 0, + width, + height, + 0, + 0, + width / 2, + height, + GL_COLOR_BUFFER_BIT, + GL_NEAREST + ); + glFramebufferTexture2D( + GL_DRAW_FRAMEBUFFER, + GL_COLOR_ATTACHMENT1, + GL_TEXTURE_2D, + static_cast(*tileTexture[1]), + 0 + ); + glBlitFramebuffer( + 0, + 0, + width / 2, + height, + 0, + 0, + width / 2, + height, + GL_COLOR_BUFFER_BIT, + GL_NEAREST + ); + return true; + }); +#endif + + reset(); +} + +void SpoutImageProvider::internalInitialize() { + ZoneScoped + +#ifdef OPENSPACE_HAS_SPOUT + spoutReceiver->updateReceiver(); +#endif // OPENSPACE_HAS_SPOUT +} + +void SpoutImageProvider::internalDeinitialize() { +#ifdef OPENSPACE_HAS_SPOUT + spoutReceiver->release(); +#endif // OPENSPACE_HAS_SPOUT +} + +Tile SpoutImageProvider::tile(const TileIndex& tileIndex) { + ZoneScoped + + spoutUpdate = true; + return tiles[tileIndex.x]; +} + +Tile::Status SpoutImageProvider::tileStatus(const TileIndex&) { + return Tile::Status::OK; +} + +TileDepthTransform SpoutImageProvider::depthTransform() { + return { 0.f, 1.f }; +} + +void SpoutImageProvider::update() { + if (!spoutUpdate) { + return; + } + + if (!spoutReceiver->isCreated()) { + reset(); + if (!spoutReceiver->isCreated()) { + return; + } + } + + spoutReceiver->updateReceiver(); +} + +void SpoutImageProvider::reset() { + spoutReceiver->updateReceiver(); +} + +int SpoutImageProvider::minLevel() { + return 0; +} + +int SpoutImageProvider::maxLevel() { + return 1; +} + +float SpoutImageProvider::noDataValueAsFloat() { + return std::numeric_limits::min(); +} + +} // namespace openspace::globebrowsing diff --git a/modules/globebrowsing/src/tileprovider/spoutimageprovider.h b/modules/globebrowsing/src/tileprovider/spoutimageprovider.h new file mode 100644 index 0000000000..326fb24ad1 --- /dev/null +++ b/modules/globebrowsing/src/tileprovider/spoutimageprovider.h @@ -0,0 +1,66 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2022 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#ifndef __OPENSPACE_MODULE_GLOBEBROWSING___TILEPROVIDER__SPOUTIMAGEPROVIDER___H__ +#define __OPENSPACE_MODULE_GLOBEBROWSING___TILEPROVIDER__SPOUTIMAGEPROVIDER___H__ + +#include + +namespace openspace::spout { class SpoutReceiverPropertyProxy; } + +namespace openspace::globebrowsing { + +class SpoutImageProvider : public TileProvider { +public: + SpoutImageProvider(const ghoul::Dictionary& dictionary); + + Tile tile(const TileIndex& tileIndex) override final; + Tile::Status tileStatus(const TileIndex& index) override final; + TileDepthTransform depthTransform() override final; + void update() override final; + void reset() override final; + int minLevel() override final; + int maxLevel() override final; + float noDataValueAsFloat() override final; + + static documentation::Documentation Documentation(); + +private: + void internalInitialize() override final; + void internalDeinitialize() override final; + + std::array, 2> tileTexture; + std::array fbo = { 0, 0 }; + std::array tiles; + +#ifdef OPENSPACE_HAS_SPOUT + std::unique_ptr spoutReceiver; +#endif + + bool spoutUpdate = false; +}; + +} // namespace openspace::globebrowsing + +#endif // __OPENSPACE_MODULE_GLOBEBROWSING___TILEPROVIDER__SPOUTIMAGEPROVIDER___H__ diff --git a/modules/globebrowsing/src/tileprovider/temporaltileprovider.cpp b/modules/globebrowsing/src/tileprovider/temporaltileprovider.cpp index dd1131cb68..77201c5c75 100644 --- a/modules/globebrowsing/src/tileprovider/temporaltileprovider.cpp +++ b/modules/globebrowsing/src/tileprovider/temporaltileprovider.cpp @@ -371,6 +371,10 @@ void TemporalTileProvider::reset() { } } +int TemporalTileProvider::minLevel() { + return 1; +} + int TemporalTileProvider::maxLevel() { if (!_currentTileProvider) { update(); @@ -833,6 +837,10 @@ void TemporalTileProvider::InterpolateTileProvider::reset() { future->reset(); } +int TemporalTileProvider::InterpolateTileProvider::minLevel() { + return glm::max(t1->minLevel(), t2->minLevel()); +} + int TemporalTileProvider::InterpolateTileProvider::maxLevel() { return glm::min(t1->maxLevel(), t2->maxLevel()); } diff --git a/modules/globebrowsing/src/tileprovider/temporaltileprovider.h b/modules/globebrowsing/src/tileprovider/temporaltileprovider.h index 469251ac4c..4e99809273 100644 --- a/modules/globebrowsing/src/tileprovider/temporaltileprovider.h +++ b/modules/globebrowsing/src/tileprovider/temporaltileprovider.h @@ -51,6 +51,7 @@ public: TileDepthTransform depthTransform() override final; void update() override final; void reset() override final; + int minLevel() override final; int maxLevel() override final; float noDataValueAsFloat() override final; @@ -71,6 +72,7 @@ private: TileDepthTransform depthTransform() override final; void update() override final; void reset() override final; + int minLevel() override final; int maxLevel() override final; float noDataValueAsFloat() override final; diff --git a/modules/globebrowsing/src/tileprovider/tileindextileprovider.cpp b/modules/globebrowsing/src/tileprovider/tileindextileprovider.cpp index 30489b9500..14850548d6 100644 --- a/modules/globebrowsing/src/tileprovider/tileindextileprovider.cpp +++ b/modules/globebrowsing/src/tileprovider/tileindextileprovider.cpp @@ -54,6 +54,10 @@ TileDepthTransform TileIndexTileProvider::depthTransform() { void TileIndexTileProvider::update() {} +int TileIndexTileProvider::minLevel() { + return 1; +} + int TileIndexTileProvider::maxLevel() { return 1337; // unlimited } diff --git a/modules/globebrowsing/src/tileprovider/tileindextileprovider.h b/modules/globebrowsing/src/tileprovider/tileindextileprovider.h index 0913b59fe3..1f8fa84467 100644 --- a/modules/globebrowsing/src/tileprovider/tileindextileprovider.h +++ b/modules/globebrowsing/src/tileprovider/tileindextileprovider.h @@ -37,6 +37,7 @@ public: Tile::Status tileStatus(const TileIndex& index) override final; TileDepthTransform depthTransform() override final; void update() override final; + int minLevel() override final; int maxLevel() override final; float noDataValueAsFloat() override final; }; diff --git a/modules/globebrowsing/src/tileprovider/tileprovider.cpp b/modules/globebrowsing/src/tileprovider/tileprovider.cpp index 324dda08f5..7b58c91fd3 100644 --- a/modules/globebrowsing/src/tileprovider/tileprovider.cpp +++ b/modules/globebrowsing/src/tileprovider/tileprovider.cpp @@ -190,7 +190,8 @@ ChunkTile TileProvider::chunkTile(TileIndex tileIndex, int parents, int maxParen // Step 3. Traverse 0 or more parents up the chunkTree until we find a chunk that // has a loaded tile ready to use. - while (tileIndex.level > 1) { + int minimumLevel = minLevel(); + while (tileIndex.level > minimumLevel) { Tile t = tile(tileIndex); if (t.status != Tile::Status::OK) { if (--maxParents < 0) { diff --git a/modules/globebrowsing/src/tileprovider/tileprovider.h b/modules/globebrowsing/src/tileprovider/tileprovider.h index ce9b63aa4e..25698c9caf 100644 --- a/modules/globebrowsing/src/tileprovider/tileprovider.h +++ b/modules/globebrowsing/src/tileprovider/tileprovider.h @@ -114,6 +114,12 @@ struct TileProvider : public properties::PropertyOwner { */ virtual void reset() = 0; + /** + * \return The minimum level as defined by the TileIndex that this + * TileProvider is capable of providing. + */ + virtual int minLevel() = 0; + /** * \return The maximum level as defined by TileIndex that this * TileProvider is able provide. diff --git a/modules/globebrowsing/src/tileprovider/tileproviderbyindex.cpp b/modules/globebrowsing/src/tileprovider/tileproviderbyindex.cpp index f2e416e39d..4e651fd9f7 100644 --- a/modules/globebrowsing/src/tileprovider/tileproviderbyindex.cpp +++ b/modules/globebrowsing/src/tileprovider/tileproviderbyindex.cpp @@ -142,6 +142,10 @@ void TileProviderByIndex::reset() { _defaultTileProvider->reset(); } +int TileProviderByIndex::minLevel() { + return 1; +} + int TileProviderByIndex::maxLevel() { return _defaultTileProvider->maxLevel(); } diff --git a/modules/globebrowsing/src/tileprovider/tileproviderbyindex.h b/modules/globebrowsing/src/tileprovider/tileproviderbyindex.h index 9af02b6378..af3fad17ef 100644 --- a/modules/globebrowsing/src/tileprovider/tileproviderbyindex.h +++ b/modules/globebrowsing/src/tileprovider/tileproviderbyindex.h @@ -38,6 +38,7 @@ public: TileDepthTransform depthTransform() override final; void update() override final; void reset() override final; + int minLevel() override final; int maxLevel() override final; float noDataValueAsFloat() override final; diff --git a/modules/globebrowsing/src/tileprovider/tileproviderbylevel.cpp b/modules/globebrowsing/src/tileprovider/tileproviderbylevel.cpp index 326a5a0f09..ea2243d3e2 100644 --- a/modules/globebrowsing/src/tileprovider/tileproviderbylevel.cpp +++ b/modules/globebrowsing/src/tileprovider/tileproviderbylevel.cpp @@ -156,6 +156,10 @@ void TileProviderByLevel::reset() { } } +int TileProviderByLevel::minLevel() { + return 1; +} + int TileProviderByLevel::maxLevel() { return static_cast(_providerIndices.size() - 1); } diff --git a/modules/globebrowsing/src/tileprovider/tileproviderbylevel.h b/modules/globebrowsing/src/tileprovider/tileproviderbylevel.h index 1020c5d29d..988b1eccb2 100644 --- a/modules/globebrowsing/src/tileprovider/tileproviderbylevel.h +++ b/modules/globebrowsing/src/tileprovider/tileproviderbylevel.h @@ -38,6 +38,7 @@ public: TileDepthTransform depthTransform() override final; void update() override final; void reset() override final; + int minLevel() override final; int maxLevel() override final; float noDataValueAsFloat() override final; diff --git a/modules/spout/CMakeLists.txt b/modules/spout/CMakeLists.txt index 13cfb4e347..ae1cdfc329 100644 --- a/modules/spout/CMakeLists.txt +++ b/modules/spout/CMakeLists.txt @@ -26,14 +26,18 @@ include(${OPENSPACE_CMAKE_EXT_DIR}/module_definition.cmake) set(HEADER_FILES renderableplanespout.h + renderablespherespout.h screenspacespout.h spoutlibrary.h + spoutwrapper.h ) source_group("Header Files" FILES ${HEADER_FILES}) set(SOURCE_FILES renderableplanespout.cpp + renderablespherespout.cpp screenspacespout.cpp + spoutwrapper.cpp ) source_group("Source Files" FILES ${SOURCE_FILES}) diff --git a/modules/spout/ext/spout/SpoutLibrary.h b/modules/spout/ext/spout/SpoutLibrary.h index d50c0a1106..180ef779ed 100644 --- a/modules/spout/ext/spout/SpoutLibrary.h +++ b/modules/spout/ext/spout/SpoutLibrary.h @@ -5,7 +5,9 @@ // #include +#ifndef SPOUT_NO_GL_INCLUDE #include +#endif #define SPOUTLIBRARY_EXPORTS // defined for this DLL. The application imports rather than exports diff --git a/modules/spout/renderableplanespout.cpp b/modules/spout/renderableplanespout.cpp index 550edc3986..90d160e350 100644 --- a/modules/spout/renderableplanespout.cpp +++ b/modules/spout/renderableplanespout.cpp @@ -71,10 +71,7 @@ documentation::Documentation RenderablePlaneSpout::Documentation() { RenderablePlaneSpout::RenderablePlaneSpout(const ghoul::Dictionary& dictionary) : RenderablePlane(dictionary) - , _spoutName(NameInfo) - , _spoutSelection(SelectionInfo) - , _updateSelection(UpdateInfo) - , _receiver(GetSpout()) + , _spoutReceiver(*this, dictionary) { const Parameters p = codegen::bake(dictionary); @@ -96,115 +93,36 @@ RenderablePlaneSpout::RenderablePlaneSpout(const ghoul::Dictionary& dictionary) // Adding an extra space to the user-facing name as it looks nicer setGuiName("RenderablePlaneSpout " + std::to_string(iIdentifier)); } - - _spoutName = p.spoutName.value_or(_spoutName); - _spoutName.onChange([this]() { - _isSpoutDirty = true; - _isErrorMessageDisplayed = false; - - _receiver->SetActiveSender(_spoutName.value().c_str()); - }); - addProperty(_spoutName); - - _spoutSelection.onChange([this](){ - _spoutName = _spoutSelection.option().description; - }); - _spoutSelection.addOption(0, ""); - addProperty(_spoutSelection); - - _updateSelection.onChange([this]() { - const std::string& currentValue = _spoutSelection.options().empty() ? - "" : - _spoutSelection.option().description; - - _spoutSelection.clearOptions(); - _spoutSelection.addOption(0, ""); - - const int nSenders = _receiver->GetSenderCount(); - int idx = 0; - - for (int i = 0; i < nSenders; ++i) { - char Name[256]; - _receiver->GetSenderName(i, Name, 256); - - _spoutSelection.addOption(i + 1, Name); - - if (currentValue == Name) { - idx = i + 1; - } - } - - _spoutSelection = idx; - }); - addProperty(_updateSelection); } void RenderablePlaneSpout::deinitializeGL() { - _receiver->ReleaseReceiver(); - _receiver->Release(); + _spoutReceiver.release(); RenderablePlane::deinitializeGL(); } void RenderablePlaneSpout::update(const UpdateData& data) { RenderablePlane::update(data); - - if (_isFirstUpdate) { - // Trigger an update; the value is a dummy that is ignored - _updateSelection.set(0); - - // #0 is the empty string and we just pick the first one after that (if it exists) - if (_spoutSelection.options().size() > 1) { - _spoutSelection = 1; - } - - _isFirstUpdate = false; - } - - if (_spoutName.value().empty()) { - return; - } - - if (_isSpoutDirty) { - defer { _isSpoutDirty = false; }; - - std::memset(_currentSenderName, 0, 256); - unsigned int width; - unsigned int height; - - _receiver->ReleaseReceiver(); - - _receiver->GetActiveSender(_currentSenderName); - - bool hasCreated = _receiver->CreateReceiver(_currentSenderName, width, height); - if (!hasCreated) { - LWARNINGC( - LoggerCat, - fmt::format("Could not create receiver for {}", _currentSenderName) - ); - return; - } - } - - unsigned int width; - unsigned int height; - const bool hasReceived = _receiver->ReceiveTexture(_currentSenderName, width, height); - - if (!hasReceived && !_isErrorMessageDisplayed) { - LWARNINGC( - LoggerCat, - fmt::format("Could not receive texture for {}", _currentSenderName) - ); - _isErrorMessageDisplayed = true; - } + _spoutReceiver.updateReceiver(); } void RenderablePlaneSpout::bindTexture() { - _receiver->BindSharedTexture(); + if (_spoutReceiver.isReceiving()) { + _spoutReceiver.saveGLTextureState(); + glBindTexture(GL_TEXTURE_2D, static_cast(_spoutReceiver.spoutTexture())); + } + else { + RenderablePlane::bindTexture(); + } } void RenderablePlaneSpout::unbindTexture() { - _receiver->UnBindSharedTexture(); + if (_spoutReceiver.isReceiving()) { + _spoutReceiver.restoreGLTextureState(); + } + else { + RenderablePlane::unbindTexture(); + } } } // namespace openspace diff --git a/modules/spout/renderableplanespout.h b/modules/spout/renderableplanespout.h index fc52e2d7bc..0c4e6d2ae9 100644 --- a/modules/spout/renderableplanespout.h +++ b/modules/spout/renderableplanespout.h @@ -29,10 +29,7 @@ #include -#include -#include -#include -#include +#include namespace openspace { @@ -51,16 +48,7 @@ private: void bindTexture() override; void unbindTexture() override; - properties::StringProperty _spoutName; - properties::OptionProperty _spoutSelection; - properties::TriggerProperty _updateSelection; - - SPOUTHANDLE _receiver; - - bool _isSpoutDirty = true; - char _currentSenderName[256] = {}; - bool _isFirstUpdate = true; - bool _isErrorMessageDisplayed = false; + spout::SpoutReceiverPropertyProxy _spoutReceiver; }; } // namespace openspace diff --git a/modules/spout/renderablespherespout.cpp b/modules/spout/renderablespherespout.cpp new file mode 100644 index 0000000000..827f3c1a16 --- /dev/null +++ b/modules/spout/renderablespherespout.cpp @@ -0,0 +1,120 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2022 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#ifdef WIN32 + +#include + +#include +#include +#include +#include +#include + +namespace openspace { + +documentation::Documentation RenderableSphereSpout::Documentation() { + using namespace openspace::documentation; + return { + "Renderable Sphere Spout", + "spout_sphere_spout", + { + { + "Name", + new StringVerifier, + Optional::Yes, + "Specifies the GUI name of the RenderableSphereSpout" + }, + { + spout::SpoutReceiverPropertyProxy::NameInfoProperty().identifier, + new StringVerifier, + Optional::Yes, + spout::SpoutReceiverPropertyProxy::NameInfoProperty().description + } + } + }; +} + +RenderableSphereSpout::RenderableSphereSpout(const ghoul::Dictionary& dictionary) + : RenderableSphere(dictionary) + , _spoutReceiver(*this, dictionary) +{ + documentation::testSpecificationAndThrow( + Documentation(), + dictionary, + "RenderableSphereSpout" + ); + + int iIdentifier = 0; + if (_identifier.empty()) { + static int id = 0; + iIdentifier = id; + + if (iIdentifier == 0) { + setIdentifier("RenderableSphereSpout"); + } + else { + setIdentifier("RenderableSphereSpout" + std::to_string(iIdentifier)); + } + ++id; + } + + if (_guiName.empty()) { + // Adding an extra space to the user-facing name as it looks nicer + setGuiName("RenderableSphereSpout " + std::to_string(iIdentifier)); + } +} + +void RenderableSphereSpout::deinitializeGL() { + _spoutReceiver.release(); + RenderableSphere::deinitializeGL(); +} + +void RenderableSphereSpout::update(const UpdateData& data) { + RenderableSphere::update(data); + _spoutReceiver.updateReceiver(); +} + +void RenderableSphereSpout::bindTexture() { + if (_spoutReceiver.isReceiving()) { + _spoutReceiver.saveGLTextureState(); + glBindTexture(GL_TEXTURE_2D, _spoutReceiver.spoutTexture()); + } + else { + RenderableSphere::bindTexture(); + } +} + +void RenderableSphereSpout::unbindTexture() { + if (_spoutReceiver.isReceiving()) { + _spoutReceiver.restoreGLTextureState(); + } + else { + RenderableSphere::unbindTexture(); + } +} + +} // namespace openspace + +#endif // WIN32 diff --git a/modules/spout/renderablespherespout.h b/modules/spout/renderablespherespout.h new file mode 100644 index 0000000000..18c16b2412 --- /dev/null +++ b/modules/spout/renderablespherespout.h @@ -0,0 +1,58 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2022 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#ifndef __OPENSPACE_MODULE_SPOUT___RENDERABLESPHERESPOUT___H__ +#define __OPENSPACE_MODULE_SPOUT___RENDERABLESPHERESPOUT___H__ + +#ifdef WIN32 + +#include + +#include + +namespace openspace { + +namespace documentation { struct Documentation; } + +class RenderableSphereSpout : public RenderableSphere { +public: + RenderableSphereSpout(const ghoul::Dictionary& dictionary); + + void deinitializeGL() override; + void update(const UpdateData& data) override; + + static documentation::Documentation Documentation(); + +private: + void bindTexture() override; + void unbindTexture() override; + + spout::SpoutReceiverPropertyProxy _spoutReceiver; +}; + +} // namespace openspace + +#endif // WIN32 + +#endif // __OPENSPACE_MODULE_SPOUT___RENDERABLESPHERESPOUT___H__ diff --git a/modules/spout/screenspacespout.cpp b/modules/spout/screenspacespout.cpp index 898a2a443f..27d0dab270 100644 --- a/modules/spout/screenspacespout.cpp +++ b/modules/spout/screenspacespout.cpp @@ -69,13 +69,8 @@ documentation::Documentation ScreenSpaceSpout::Documentation() { ScreenSpaceSpout::ScreenSpaceSpout(const ghoul::Dictionary& dictionary) : ScreenSpaceRenderable(dictionary) - , _spoutName(NameInfo) - , _spoutSelection(SelectionInfo) - , _updateSelection(UpdateInfo) - , _receiver(GetSpout()) + , _spoutReceiver(*this, dictionary) { - const Parameters p = codegen::bake(dictionary); - std::string identifier; if (dictionary.hasValue(KeyIdentifier)) { identifier = dictionary.value(KeyIdentifier); @@ -85,127 +80,30 @@ ScreenSpaceSpout::ScreenSpaceSpout(const ghoul::Dictionary& dictionary) } identifier = makeUniqueIdentifier(identifier); setIdentifier(std::move(identifier)); - - _spoutName = p.spoutName.value_or(_spoutName); - _spoutName.onChange([this]() { - _isSpoutDirty = true; - _isErrorMessageDisplayed = false; - - _receiver->SetActiveSender(_spoutName.value().c_str()); - }); - addProperty(_spoutName); - - _spoutSelection.onChange([this]() { - _spoutName = _spoutSelection.option().description; - }); - _spoutSelection.addOption(0, ""); - addProperty(_spoutSelection); - - _updateSelection.onChange([this]() { - const std::string& currentValue = _spoutSelection.options().empty() ? - "" : - _spoutSelection.option().description; - - _spoutSelection.clearOptions(); - _spoutSelection.addOption(0, ""); - - int nSenders = _receiver->GetSenderCount(); - - int idx = 0; - - for (int i = 0; i < nSenders; ++i) { - char Name[256]; - _receiver->GetSenderName(i, Name, 256); - - _spoutSelection.addOption(i + 1, Name); - - if (currentValue == Name) { - idx = i + 1; - } - } - - _spoutSelection = idx; - }); - addProperty(_updateSelection); } bool ScreenSpaceSpout::deinitializeGL() { - _receiver->ReleaseReceiver(); - _receiver->Release(); + _spoutReceiver.release(); return ScreenSpaceRenderable::deinitializeGL(); } bool ScreenSpaceSpout::isReady() const { - return ScreenSpaceRenderable::isReady() && !_spoutName.value().empty(); + return ScreenSpaceRenderable::isReady() && !_spoutReceiver.isReceiving(); } void ScreenSpaceSpout::update() { - if (_isFirstUpdate) { - defer { _isFirstUpdate = false; }; - - // Trigger an update; the value is a dummy that is ignored - _updateSelection.set(0); - - // #0 is the empty string and we just pick the first one after that (if it exists) - if (_spoutSelection.options().size() > 1) { - _spoutSelection = 1; - } - } - - if (_spoutName.value().empty()) { - return; - } - - if (_isSpoutDirty) { - defer { _isSpoutDirty = false; }; - - std::memset(_currentSenderName, 0, 256); - - _receiver->ReleaseReceiver(); - _receiver->GetActiveSender(_currentSenderName); - - unsigned int width; - unsigned int height; - const bool hasCreated = _receiver->CreateReceiver( - _currentSenderName, - width, - height - ); - _objectSize = { width, height }; - - if (!hasCreated) { - LWARNINGC( - "ScreenSpaceSpout", - fmt::format("Could not create receiver for {}", _currentSenderName) - ); - return; - } - } - - unsigned int width; - unsigned int height; - const bool receiveSuccess = _receiver->ReceiveTexture( - _currentSenderName, - width, - height - ); - - if (!receiveSuccess && !_isErrorMessageDisplayed) { - LWARNINGC( - "ScreenSpaceSpout", - fmt::format("Could not receive texture for {}", _currentSenderName) - ); - _isErrorMessageDisplayed = true; - } + ScreenSpaceRenderable::update(); + _spoutReceiver.updateReceiver(); } void ScreenSpaceSpout::bindTexture() { - _receiver->BindSharedTexture(); + _spoutReceiver.saveGLTextureState(); + glBindTexture(GL_TEXTURE_2D, _spoutReceiver.spoutTexture()); } void ScreenSpaceSpout::unbindTexture() { - _receiver->UnBindSharedTexture(); + _spoutReceiver.restoreGLTextureState(); } } // namespace openspace diff --git a/modules/spout/screenspacespout.h b/modules/spout/screenspacespout.h index 299ac2e11a..fd4a764894 100644 --- a/modules/spout/screenspacespout.h +++ b/modules/spout/screenspacespout.h @@ -29,11 +29,7 @@ #include -#include - -#include -#include -#include +#include namespace openspace { @@ -54,17 +50,8 @@ public: private: void bindTexture() override; void unbindTexture() override; - - properties::StringProperty _spoutName; - properties::OptionProperty _spoutSelection; - properties::TriggerProperty _updateSelection; - - SPOUTHANDLE _receiver; - - bool _isSpoutDirty = true; - char _currentSenderName[256] = {}; - bool _isFirstUpdate = true; - bool _isErrorMessageDisplayed = false; + + spout::SpoutReceiverPropertyProxy _spoutReceiver; }; } // namespace openspace diff --git a/modules/spout/spoutmodule.cpp b/modules/spout/spoutmodule.cpp index 69d37205d9..55ece03384 100644 --- a/modules/spout/spoutmodule.cpp +++ b/modules/spout/spoutmodule.cpp @@ -25,6 +25,7 @@ #include #include +#include #include #include #include @@ -46,6 +47,7 @@ void SpoutModule::internalInitialize(const ghoul::Dictionary&) { FactoryManager::ref().factory(); ghoul_assert(fRenderable, "Renderable factory was not created"); fRenderable->registerClass("RenderablePlaneSpout"); + fRenderable->registerClass("RenderableSphereSpout"); #endif // WIN32 } diff --git a/modules/spout/spoutwrapper.cpp b/modules/spout/spoutwrapper.cpp new file mode 100644 index 0000000000..7b5ac35f53 --- /dev/null +++ b/modules/spout/spoutwrapper.cpp @@ -0,0 +1,613 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2022 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#include "modules/spout/spoutwrapper.h" + +#include +#include +#include +#include +#define SPOUT_NO_GL_INCLUDE +#include + +namespace { + constexpr const char _loggerCat[] = "Spout"; + + constexpr openspace::properties::Property::PropertyInfo NameSenderInfo = { + "SpoutName", + "Spout Sender Name", + "This value sets the Spout sender to use a specific name." + }; + + constexpr openspace::properties::Property::PropertyInfo NameReceiverInfo = { + "SpoutName", + "Spout Receiver Name", + "This value explicitly sets the Spout receiver to use a specific name. If this " + "is not a valid name, an empty image is used." + }; + + constexpr openspace::properties::Property::PropertyInfo SelectionInfo = { + "SpoutSelection", + "Spout Selection", + "This property displays all available Spout sender on the system. If one them is " + "selected, its value is stored in the 'SpoutName' property, overwriting its " + "previous value." + }; + + constexpr openspace::properties::Property::PropertyInfo UpdateInfo = { + "UpdateSelection", + "Update Selection", + "If this property is trigged, the 'SpoutSelection' options will be refreshed." + }; +} // namespace + +namespace openspace::spout { + +SpoutMain::SpoutMain() { + _spoutHandle = GetSpout(); +} + +SpoutMain::~SpoutMain() {} + +void SpoutMain::release() { + if (_spoutHandle) { + _spoutHandle->Release(); + } +} + +void SpoutMain::saveGLState() { + GLint buf; + glGetIntegerv(GL_FRAMEBUFFER_BINDING, &buf); + _defaultFBO = static_cast(buf); + + glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &buf); + _defaultReadFBO = static_cast(buf); + + glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &buf); + _defaultDrawFBO = static_cast(buf); + + glGetIntegerv(GL_READ_BUFFER, &buf); + _defaultReadBuffer = static_cast(buf); + + glGetIntegerv(GL_DRAW_BUFFER0, &buf); + _defaultReadBuffer = static_cast(buf); + + saveGLTextureState(); +} + +void SpoutMain::restoreGLState() { + glBindFramebuffer(GL_FRAMEBUFFER, static_cast(_defaultFBO)); + if (_defaultFBO) { + glBindFramebuffer(GL_READ_FRAMEBUFFER, static_cast(_defaultReadFBO)); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, static_cast(_defaultDrawFBO)); + glReadBuffer(static_cast(_defaultReadBuffer)); + GLenum buf[1]; + buf[0] = static_cast(_defaultDrawBuffer[0]); + glDrawBuffers(1, buf); + } + restoreGLTextureState(); +} + +void SpoutMain::saveGLTextureState() { + GLint buf; + glGetIntegerv(GL_TEXTURE_BINDING_2D, &buf); + _defaultTexture = static_cast(buf); +} + +void SpoutMain::restoreGLTextureState() { + glBindTexture(GL_TEXTURE_2D, static_cast(_defaultTexture)); +} + +SpoutReceiver::SpoutReceiver() {} + +SpoutReceiver::~SpoutReceiver() {} + +const std::vector &SpoutReceiver::spoutReceiverList() { + if (!_spoutHandle) { + return _receiverList; + } + + const int nSenders = _spoutHandle->GetSenderCount(); + _receiverList.clear(); + + for (int i = 0; i < nSenders; ++i) { + char Name[256]; + _spoutHandle->GetSenderName(i, Name, 256); + _receiverList.push_back(Name); + } + + return _receiverList; +} + +bool SpoutReceiver::isCreated() const { + return _isCreated; +} + +bool SpoutReceiver::isReceiving() const { + return _isReceiving; +} + +bool SpoutReceiver::updateReceiver() { + unsigned int width = 10; + unsigned int height = 10; + + if (!_spoutHandle || !_isCreated) { + return false; + } + + char currentSpoutName[256] = { 0 }; + std::memcpy(currentSpoutName, _currentSpoutName.data(), _currentSpoutName.size()); + _spoutHandle->CheckReceiver(currentSpoutName, width, height, _isReceiving); + + // if spout is not connected a 10x10 texture is created + if (updateTexture(width, height) && _isReceiving) { + saveGLState(); + + _spoutHandle->ReceiveTexture( + currentSpoutName, + width, + height, + static_cast(*_spoutTexture), + static_cast(GL_TEXTURE_2D), + true + ); + + if (_onUpdateReceiverCallback) { + const GLuint t = static_cast(*_spoutTexture); + if (!_onUpdateReceiverCallback(width, height, t)) { + restoreGLState(); + return false; + } + } + + restoreGLState(); + return true; + } + + return false; +} + +bool SpoutReceiver::updateReceiverName(const std::string& name) { + unsigned int width = 0; + unsigned int height = 0; + + if (!_spoutHandle) { + return false; + } + + releaseReceiver(); + + if (_onUpdateReceiverNameCallback) { + if (!_onUpdateReceiverNameCallback(name)) { + return false; + } + } + + char nameBuf[256] = { 0 }; + std::memcpy(nameBuf, name.data(), name.size()); + bool hasCreated = _spoutHandle->CreateReceiver(nameBuf, width, height); + if (!hasCreated) { + if (!_isErrorMessageDisplayed) { + LWARNING(fmt::format( + "Could not create receiver for {} -> {}x{}", name, width, height + )); + _isErrorMessageDisplayed = true; + } + return false; + } + + _currentSpoutName = name; + _isErrorMessageDisplayed = false; + _isCreated = true; + + return true; +} + +void SpoutReceiver::releaseReceiver() { + if (!_isCreated) { + return; + } + + _isReceiving = false; + _isCreated = false; + _isErrorMessageDisplayed = false; + _currentSpoutName.clear(); + if (_onReleaseReceiverCallback) { + _onReleaseReceiverCallback(); + } + releaseTexture(); + if (_spoutHandle) { + _spoutHandle->ReleaseReceiver(); + } +} + +void SpoutReceiver::release() { + releaseReceiver(); + SpoutMain::release(); +} + +void SpoutReceiver::onUpdateReceiverName(std::function callback) +{ + _onUpdateReceiverNameCallback = std::move(callback); +} + +void SpoutReceiver::onUpdateReceiver(std::function callback) +{ + _onUpdateReceiverCallback = std::move(callback); +} + +void SpoutReceiver::onReleaseReceiver(std::function callback) { + _onReleaseReceiverCallback = std::move(callback); +} + +void SpoutReceiver::onUpdateTexture(std::function callback) { + _onUpdateTextureCallback = std::move(callback); +} + +void SpoutReceiver::onReleaseTexture(std::function callback) { + _onReleaseTextureCallback = std::move(callback); +} + +unsigned int SpoutReceiver::spoutTexture() const { + return _spoutTexture ? static_cast(*_spoutTexture) : 0; +} + +bool SpoutReceiver::updateTexture(unsigned int width, unsigned int height) { + if (width != _spoutWidth || height != _spoutHeight) { + releaseTexture(); + _spoutTexture = std::make_unique( + glm::uvec3(width, height, 1), + GL_TEXTURE_2D, + ghoul::opengl::Texture::Format::RGBA, + GL_RGBA, GL_UNSIGNED_BYTE, + ghoul::opengl::Texture::FilterMode::Linear, + ghoul::opengl::Texture::WrappingMode::Repeat, + ghoul::opengl::Texture::AllocateData::No, + ghoul::opengl::Texture::TakeOwnership::No + ); + + if (_spoutTexture) { + _spoutTexture->uploadTexture(); + if (_onUpdateTextureCallback && !_onUpdateTextureCallback(width, height)) { + LWARNING(fmt::format( + "Could not create callback texture for {} -> {}x{}", + _currentSpoutName, width, height + )); + return false; + } + _spoutWidth = width; + _spoutHeight = height; + } + else { + LWARNING(fmt::format( + "Could not create texture for {} -> {}x{}", + _currentSpoutName, width, height + )); + return false; + } + } + return true; +} + +void SpoutReceiver::releaseTexture() { + _spoutWidth = 0; + _spoutHeight = 0; + if (_onReleaseTextureCallback) { + _onReleaseTextureCallback(); + } + _spoutTexture.release(); +} + +const properties::Property::PropertyInfo& SpoutReceiverPropertyProxy::NameInfoProperty() { + return NameReceiverInfo; +} + +const properties::Property::PropertyInfo& +SpoutReceiverPropertyProxy::SelectionInfoProperty() +{ + return SelectionInfo; +} + +const properties::Property::PropertyInfo& SpoutReceiverPropertyProxy::UpdateInfoProperty() +{ + return UpdateInfo; +} + +SpoutReceiverPropertyProxy::SpoutReceiverPropertyProxy(properties::PropertyOwner& owner, + const ghoul::Dictionary& dictionary) + : _spoutName(NameReceiverInfo) + , _spoutSelection(SelectionInfo) + , _updateSelection(UpdateInfo) +{ + if (dictionary.hasKey(NameReceiverInfo.identifier)) { + _spoutName = dictionary.value(NameReceiverInfo.identifier); + } + else { + _isSelectAny = true; + } + + _spoutName.onChange([this]() { _isSpoutDirty = true; }); + owner.addProperty(_spoutName); + + _spoutSelection.onChange([this]() { + if (_spoutName.value().empty() && _spoutSelection.value() == 0) { + if (_spoutSelection.options().size() > 1) { + _spoutSelection = 1; + } + } + _spoutName = ""; + _spoutName = _spoutSelection.option().description; + }); + _spoutSelection.addOption(0, ""); + owner.addProperty(_spoutSelection); + + _updateSelection.onChange([this]() { + const std::vector receiverList = spoutReceiverList(); + + _spoutSelection.clearOptions(); + _spoutSelection.addOption(0, ""); + + int idx = 0; + for (int i = 0; i < static_cast(receiverList.size()); ++i) { + _spoutSelection.addOption(i + 1, receiverList[i]); + + LWARNING(fmt::format("List {}", receiverList[i])); + + if (!_isSelectAny && _spoutName.value() == receiverList[i]) { + idx = i + 1; + } + } + _spoutSelection = idx; + + }); + owner.addProperty(_updateSelection); + + _updateSelection.set(0); +} + +SpoutReceiverPropertyProxy::~SpoutReceiverPropertyProxy() {} + +bool SpoutReceiverPropertyProxy::updateReceiver() { + if (_isSpoutDirty) { + if (!updateReceiverName(_spoutName.value())) { + return false; + } + _isSpoutDirty = false; + } + return SpoutReceiver::updateReceiver(); +} + +void SpoutReceiverPropertyProxy::releaseReceiver() { + _isSpoutDirty = true; + SpoutReceiver::releaseReceiver(); +} + + +SpoutSender::SpoutSender() {} + +SpoutSender::~SpoutSender() {} + +bool SpoutSender::isCreated() const { + return _isCreated; +} + +bool SpoutSender::isSending() const { + return _isSending; +} + +bool SpoutSender::updateSenderStatus() { + if (!_isSending) { + if (_spoutWidth == 0 || _spoutHeight == 0) { + if (!_isErrorMessageDisplayed) { + LWARNING(fmt::format( + "Could not create sender for {}, dimensions invalid {}x{}", + _currentSpoutName, _spoutWidth, _spoutHeight + )); + _isErrorMessageDisplayed = true; + } + return false; + } + + if (_currentSpoutName.empty()) { + if (!_isErrorMessageDisplayed) { + LWARNING(fmt::format("Could not create sender, invalid name")); + _isErrorMessageDisplayed = true; + } + return false; + } + + ghoul_assert(_currentSpoutName.size() < 256, "Spout name must be < 256"); + char name[256] = { 0 }; + std::memcpy(name, _currentSpoutName.data(), _currentSpoutName.size()); + + bool hasCreated = _spoutHandle->CreateSender(name, _spoutWidth, _spoutHeight); + if (!hasCreated) { + if (!_isErrorMessageDisplayed) { + LWARNING(fmt::format( + "Could not create sender for {} -> {}x{}", + _currentSpoutName, _spoutWidth, _spoutHeight + )); + _isErrorMessageDisplayed = true; + } + return false; + } + } + + _isErrorMessageDisplayed = false; + _isSending = true; + + return true; +} + +bool SpoutSender::updateSender(unsigned int texture, unsigned int textureType) { + if (!_spoutHandle || !updateSenderStatus()) { + return false; + } + + _spoutHandle->SendTexture(texture, textureType, _spoutWidth, _spoutHeight); + + if (_onUpdateSenderCallback) { + bool s = _onUpdateSenderCallback( + _currentSpoutName, + texture, + textureType, + _spoutWidth, + _spoutHeight + ); + if (!s) { + return false; + } + } + + return true; +} + +bool SpoutSender::updateSenderName(const std::string& name) { + if (!_spoutHandle) { + return false; + } + if (name == _currentSpoutName) { + return true; + } + + releaseSender(); + + if (_onUpdateSenderNameCallback) { + if (!_onUpdateSenderNameCallback(name)) { + return false; + } + } + + _currentSpoutName = name; + _isCreated = true; + + return true; +} + +bool SpoutSender::updateSenderSize(int width, int height) { + if (!_spoutHandle) { + return false; + } + if (width == static_cast(_spoutWidth) && + height == static_cast(_spoutHeight)) + { + return true; + } + + releaseSender(); + + if (_onUpdateSenderSizeCallback) { + if (!_onUpdateSenderSizeCallback(width, height)) { + return false; + } + } + + _spoutWidth = width; + _spoutHeight = height; + _isCreated = true; + + return true; +} + +void SpoutSender::releaseSender() { + if (!_isSending) { + return; + } + + _isCreated = false; + _isSending = false; + _isErrorMessageDisplayed = false; + _currentSpoutName.clear(); + _spoutWidth = 0; + _spoutHeight = 0; + if (_onReleaseSenderCallback) { + _onReleaseSenderCallback(); + } + if (_spoutHandle) { + _spoutHandle->ReleaseReceiver(); + } +} + +void SpoutSender::release() { + releaseSender(); + SpoutMain::release(); +} + +void SpoutSender::onUpdateSenderName(std::function callback) { + _onUpdateSenderNameCallback = std::move(callback); +} + +void SpoutSender::onUpdateSenderSize(std::function callback) { + _onUpdateSenderSizeCallback = std::move(callback); +} + +void SpoutSender::onUpdateSender(std::function callback) +{ + _onUpdateSenderCallback = std::move(callback); +} + +void SpoutSender::onReleaseSender(std::function callback) { + _onReleaseSenderCallback = std::move(callback); +} + +const properties::Property::PropertyInfo& SpoutSenderPropertyProxy::NameInfoProperty() { + return NameSenderInfo; +} + +SpoutSenderPropertyProxy::SpoutSenderPropertyProxy(properties::PropertyOwner& owner, + const ghoul::Dictionary& dictionary) + : _spoutName(NameSenderInfo) +{ + if (dictionary.hasKey(NameSenderInfo.identifier)) { + _spoutName = dictionary.value(NameSenderInfo.identifier); + } + else { + LWARNING(fmt::format("Sender does not have a name")); + } + + _spoutName.onChange([this]() { _isSpoutDirty = true; }); + owner.addProperty(_spoutName); +} + +SpoutSenderPropertyProxy::~SpoutSenderPropertyProxy() {} + +bool SpoutSenderPropertyProxy::updateSender(unsigned int texture, + unsigned int textureType) +{ + if (_isSpoutDirty) { + if (!updateSenderName(_spoutName)) { + return false; + } + _isSpoutDirty = false; + } + return SpoutSender::updateSender(texture, textureType); +} + +void SpoutSenderPropertyProxy::releaseSender() { + _isSpoutDirty = true; + SpoutSender::releaseSender(); +} + +} // namespace openspace::spout diff --git a/modules/spout/spoutwrapper.h b/modules/spout/spoutwrapper.h new file mode 100644 index 0000000000..db315cccd3 --- /dev/null +++ b/modules/spout/spoutwrapper.h @@ -0,0 +1,190 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2022 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#ifndef __OPENSPACE_MODULE_SPOUT___SPOUTWRAPPER___H__ +#define __OPENSPACE_MODULE_SPOUT___SPOUTWRAPPER___H__ + +#include +#include +#include +#include +#include +#include + +struct SPOUTLIBRARY; +typedef SPOUTLIBRARY* SPOUTHANDLE; + +namespace ghoul::opengl { class Texture; } + +namespace openspace::spout { + +// @TODO(abock, 2022-03-02) This class should probably be outsourced into a stand-alone +// library that the SGCT version of this class then can also use +class SpoutMain { +public: + SpoutMain(); + virtual ~SpoutMain(); + + virtual void release(); + + void saveGLState(); + void restoreGLState(); + + void saveGLTextureState(); + void restoreGLTextureState(); + +protected: + unsigned int _defaultTexture; + unsigned int _defaultFBO; + unsigned int _defaultReadFBO; + unsigned int _defaultDrawFBO; + unsigned int _defaultReadBuffer; + unsigned int _defaultDrawBuffer[1] = { 0 }; + + SPOUTHANDLE _spoutHandle = nullptr; + + std::string _currentSpoutName; + unsigned int _spoutWidth = 0; + unsigned int _spoutHeight = 0; +}; + +class SpoutReceiver : public SpoutMain { +public: + SpoutReceiver(); + virtual ~SpoutReceiver(); + + void release() override; + + virtual bool updateReceiverName(const std::string& name); + virtual bool updateReceiver(); + virtual void releaseReceiver(); + + void onUpdateReceiverName(std::function callback); + void onUpdateReceiver(std::function callback); + void onReleaseReceiver(std::function callback); + void onUpdateTexture(std::function callback); + void onReleaseTexture(std::function callback); + + const std::vector& spoutReceiverList(); + bool isCreated() const; + bool isReceiving() const; + unsigned int spoutTexture() const; + +private: + bool updateTexture(unsigned int width, unsigned int height); + void releaseTexture(); + + bool _isErrorMessageDisplayed = false; + bool _isCreated = false; + bool _isReceiving = false; + std::vector _receiverList; + + std::unique_ptr _spoutTexture; + + std::function _onUpdateReceiverNameCallback = nullptr; + std::function _onUpdateReceiverCallback = nullptr; + std::function _onReleaseReceiverCallback = nullptr; + std::function _onUpdateTextureCallback = nullptr; + std::function _onReleaseTextureCallback = nullptr; +}; + + +class SpoutReceiverPropertyProxy : public SpoutReceiver { +public: + static const properties::Property::PropertyInfo& NameInfoProperty(); + static const properties::Property::PropertyInfo& SelectionInfoProperty(); + static const properties::Property::PropertyInfo& UpdateInfoProperty(); + + SpoutReceiverPropertyProxy(properties::PropertyOwner& owner, + const ghoul::Dictionary& dictionary); + virtual ~SpoutReceiverPropertyProxy(); + + bool updateReceiver() override; + void releaseReceiver() override; + +private: + properties::StringProperty _spoutName; + properties::OptionProperty _spoutSelection; + properties::TriggerProperty _updateSelection; + + bool _isSpoutDirty = true; + bool _isSelectAny = false; +}; + + +class SpoutSender : public SpoutMain { +public: + SpoutSender(); + virtual ~SpoutSender(); + + void release() override; + + virtual bool updateSenderName(const std::string& name); + virtual bool updateSenderSize(int width, int height); + virtual bool updateSender(unsigned int texture, unsigned int textureType); + virtual void releaseSender(); + + void onUpdateSenderName(std::function callback); + void onUpdateSenderSize(std::function callback); + void onUpdateSender(std::function callback); + void onReleaseSender(std::function callback); + + bool isCreated() const; + bool isSending() const; + +private: + bool updateSenderStatus(); + + bool _isErrorMessageDisplayed = false; + bool _isCreated = false; + bool _isSending = false; + + std::function _onUpdateSenderNameCallback = nullptr; + std::function _onUpdateSenderSizeCallback = nullptr; + std::function _onUpdateSenderCallback = nullptr; + std::function _onReleaseSenderCallback = nullptr; +}; + +class SpoutSenderPropertyProxy : public SpoutSender { +public: + static const properties::Property::PropertyInfo& NameInfoProperty(); + + SpoutSenderPropertyProxy(properties::PropertyOwner& owner, + const ghoul::Dictionary& dictionary); + virtual ~SpoutSenderPropertyProxy(); + + bool updateSender(unsigned int texture, unsigned int textureType) override; + void releaseSender() override; + +private: + properties::StringProperty _spoutName; + + bool _isSpoutDirty = true; +}; + +} // namespace openspace::spout + +#endif // __OPENSPACE_MODULE_SPOUT___SPOUTWRAPPER___H__ diff --git a/openspace.cfg b/openspace.cfg index 9be037a435..862f9f5c27 100644 --- a/openspace.cfg +++ b/openspace.cfg @@ -20,31 +20,44 @@ SGCTConfig = sgct.config.single{vsync=false} -- Streaming OpenSpace via Spout to OBS -- SGCTConfig = sgct.config.single{2560, 1440, shared=true, name="WV_OBS_SPOUT1"} ---for more details about sgct configuration options see: +-- Elumenati Configs +-- SGCTConfig = "${CONFIG}/spout_output_flat.json" +-- SGCTConfig = "${CONFIG}/spout_output_cubemap.json" +-- SGCTConfig = "${CONFIG}/spout_output_equirectangular.json" +-- SGCTConfig = "${CONFIG}/spout_output_fisheye.json" + +-- for more details about sgct configuration options see: -- https://sgct.github.io/configuration-files.html -- To use a sgct configuration file set the variable like below -- SGCTConfig = "${CONFIG}/single_gui.xml" -- In the config/ folder we provide the some predefined files which you can modify. --- fullscreen1080.xml: fullscreen window at 1920x1080 --- gui_projector.xml: one window for the gui and a second fullscreen window for rendering +-- fullscreen1080.json: fullscreen window at 1920x1080 +-- gui_projector.json: one window for the gui and a second fullscreen window for rendering -- on the second monitor --- openvr_htcVive.xml: window for VR on HTC Vive, only works if OpenSpace is compiled +-- openvr_htcVive.json: window for VR on HTC Vive, only works if OpenSpace is compiled -- custom with the openvr support --- openvr_oculusRiftCv1.xml: window for VR on Oculus Rift, only works if OpenSpace is +-- openvr_oculusRiftCv1.json: window for VR on Oculus Rift, only works if OpenSpace is -- compiled custom with the openvr support --- single.xml: regular window --- single_fisheye.xml: regular window with fisheye rendering --- single_fisheye_gui.xml: one window for the gui, one window for fisheye rendering --- single_gui.xml: one window for the gui, one window for rendering --- single_sbs_stereo.xml: one window with side by side rendering for stereo/3d support --- single_two_win.xml: two windows with gui and rendering --- spherical_mirror.xml: one window with a spherical mirror rendering --- spherical_mirror_gui.xml: one window for the gui, and one window with a spherical +-- single.json: regular window +-- single_fisheye.json: regular window with fisheye rendering +-- single_fisheye_gui.json: one window for the gui, one window for fisheye rendering +-- single_gui.json: one window for the gui, one window for rendering +-- single_sbs_stereo.json: one window with side by side rendering for stereo/3d support +-- single_two_win.json: two windows with gui and rendering +-- spherical_mirror.json: one window with a spherical mirror rendering +-- spherical_mirror_gui.json: one window for the gui, and one window with a spherical -- mirror rendering --- spout_out.xml: a window where the rendering is sent to spout instead of the display --- two_nodes.xml: a configuration for running two instances of openspace, used for +-- two_nodes.json: a configuration for running two instances of openspace, used for -- multiple projection systems +-- spout_output_flat.json: a window where the rendering is sent to spout instead of the +-- display +-- spout_output_cubemap.json: a window where the rendering is sent to spout (max of 6 spout +-- instances) instead of the display +-- spout_output_equirectangular.json: a window where the rendering is sent to spout +-- (equirectangular output) instead of the display +-- spout_output_fisheye.json: a window where the rendering is sent to spout (fisheye +-- output) instead of the display -- Variable: Profile -- Sets the profile that should be loaded by OpenSpace.