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 <marco@elumenati.com>
This commit is contained in:
Alexander Bock
2022-03-03 23:47:09 +01:00
committed by GitHub
parent 2d3c76f222
commit 6a708b1f18
50 changed files with 1792 additions and 367 deletions

View File

@@ -79,7 +79,7 @@
#endif // OPENVR_SUPPORT
#ifdef OPENSPACE_HAS_SPOUT
#include "SpoutLibrary.h"
#include <modules/spout/spoutwrapper.h>
#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<int>(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<int>(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

View File

@@ -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 }
}
]
}

View File

@@ -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 }
}
]
}

View File

@@ -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 }
}
]
}

View File

@@ -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 }
}
]
}

View File

@@ -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 }
}
]
}

View File

@@ -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 }
}
]
}

View File

@@ -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)

View File

@@ -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<ghoul::opengl::Texture> texture =

View File

@@ -60,6 +60,10 @@ public:
static documentation::Documentation Documentation();
protected:
virtual void bindTexture();
virtual void unbindTexture();
private:
void loadTexture();

View File

@@ -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

View File

@@ -39,6 +39,7 @@
#include <modules/globebrowsing/src/tileprovider/imagesequencetileprovider.h>
#include <modules/globebrowsing/src/tileprovider/singleimagetileprovider.h>
#include <modules/globebrowsing/src/tileprovider/sizereferencetileprovider.h>
#include <modules/globebrowsing/src/tileprovider/spoutimageprovider.h>
#include <modules/globebrowsing/src/tileprovider/temporaltileprovider.h>
#include <modules/globebrowsing/src/tileprovider/tileindextileprovider.h>
#include <modules/globebrowsing/src/tileprovider/tileprovider.h>
@@ -306,6 +307,9 @@ void GlobeBrowsingModule::internalInitialize(const ghoul::Dictionary& dict) {
fTileProvider->registerClass<ImageSequenceTileProvider>(
LAYER_TYPE_NAMES[static_cast<int>(TypeID::ImageSequenceTileLayer)]
);
fTileProvider->registerClass<SpoutImageProvider>(
LAYER_TYPE_NAMES[static_cast<int>(TypeID::SpoutImageTileLayer)]
);
fTileProvider->registerClass<TemporalTileProvider>(
LAYER_TYPE_NAMES[static_cast<int>(TypeID::TemporalTileLayer)]
);

View File

@@ -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)

View File

@@ -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;

View File

@@ -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:

View File

@@ -115,7 +115,7 @@ namespace {
std::optional<std::string> 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:

View File

@@ -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;

View File

@@ -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();

View File

@@ -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;

View File

@@ -149,6 +149,10 @@ void ImageSequenceTileProvider::reset() {
}
}
int ImageSequenceTileProvider::minLevel() {
return 1;
}
int ImageSequenceTileProvider::maxLevel() {
return _currentTileProvider ? _currentTileProvider->maxLevel() : 0;
}

View File

@@ -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;

View File

@@ -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
}

View File

@@ -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;

View File

@@ -114,6 +114,10 @@ TileDepthTransform SizeReferenceTileProvider::depthTransform() {
void SizeReferenceTileProvider::update() {}
int SizeReferenceTileProvider::minLevel() {
return 1;
}
int SizeReferenceTileProvider::maxLevel() {
return 1337; // unlimited
}

View File

@@ -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;

View File

@@ -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 <modules/globebrowsing/src/tileprovider/spoutimageprovider.h>
#include <openspace/documentation/documentation.h>
#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 <modules/spout/spoutwrapper.h>
#endif
namespace {
struct [[codegen::Dictionary(SpoutImageProvider)]] Parameters {
std::optional<std::string> spoutName;
};
#include "spoutimageprovider_codegen.cpp"
} // namespace
namespace openspace::globebrowsing {
documentation::Documentation SpoutImageProvider::Documentation() {
return codegen::doc<Parameters>("globebrowsing_spoutimageprovider");
}
SpoutImageProvider::SpoutImageProvider(const ghoul::Dictionary& dictionary) {
ZoneScoped
#ifdef OPENSPACE_HAS_SPOUT
spoutReceiver = std::make_unique<spout::SpoutReceiverPropertyProxy>(
*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<ghoul::opengl::Texture>(
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<GLuint>(texture),
0
);
glReadBuffer(GL_COLOR_ATTACHMENT0);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo[1]);
glFramebufferTexture2D(
GL_DRAW_FRAMEBUFFER,
GL_COLOR_ATTACHMENT1,
GL_TEXTURE_2D,
static_cast<GLuint>(*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<GLuint>(*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<float>::min();
}
} // namespace openspace::globebrowsing

View File

@@ -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 <modules/globebrowsing/src/tileprovider/tileprovider.h>
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<std::unique_ptr<ghoul::opengl::Texture>, 2> tileTexture;
std::array<GLuint, 2> fbo = { 0, 0 };
std::array<Tile, 2> tiles;
#ifdef OPENSPACE_HAS_SPOUT
std::unique_ptr<spout::SpoutReceiverPropertyProxy> spoutReceiver;
#endif
bool spoutUpdate = false;
};
} // namespace openspace::globebrowsing
#endif // __OPENSPACE_MODULE_GLOBEBROWSING___TILEPROVIDER__SPOUTIMAGEPROVIDER___H__

View File

@@ -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());
}

View File

@@ -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;

View File

@@ -54,6 +54,10 @@ TileDepthTransform TileIndexTileProvider::depthTransform() {
void TileIndexTileProvider::update() {}
int TileIndexTileProvider::minLevel() {
return 1;
}
int TileIndexTileProvider::maxLevel() {
return 1337; // unlimited
}

View File

@@ -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;
};

View File

@@ -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) {

View File

@@ -114,6 +114,12 @@ struct TileProvider : public properties::PropertyOwner {
*/
virtual void reset() = 0;
/**
* \return The minimum level as defined by the <code>TileIndex</code> that this
* TileProvider is capable of providing.
*/
virtual int minLevel() = 0;
/**
* \return The maximum level as defined by <code>TileIndex</code> that this
* TileProvider is able provide.

View File

@@ -142,6 +142,10 @@ void TileProviderByIndex::reset() {
_defaultTileProvider->reset();
}
int TileProviderByIndex::minLevel() {
return 1;
}
int TileProviderByIndex::maxLevel() {
return _defaultTileProvider->maxLevel();
}

View File

@@ -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;

View File

@@ -156,6 +156,10 @@ void TileProviderByLevel::reset() {
}
}
int TileProviderByLevel::minLevel() {
return 1;
}
int TileProviderByLevel::maxLevel() {
return static_cast<int>(_providerIndices.size() - 1);
}

View File

@@ -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;

View File

@@ -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})

View File

@@ -5,7 +5,9 @@
//
#include <windows.h>
#ifndef SPOUT_NO_GL_INCLUDE
#include <GL/GL.h>
#endif
#define SPOUTLIBRARY_EXPORTS // defined for this DLL. The application imports rather than exports

View File

@@ -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<Parameters>(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<GLuint>(_spoutReceiver.spoutTexture()));
}
else {
RenderablePlane::bindTexture();
}
}
void RenderablePlaneSpout::unbindTexture() {
_receiver->UnBindSharedTexture();
if (_spoutReceiver.isReceiving()) {
_spoutReceiver.restoreGLTextureState();
}
else {
RenderablePlane::unbindTexture();
}
}
} // namespace openspace

View File

@@ -29,10 +29,7 @@
#include <modules/base/rendering/renderableplane.h>
#include <modules/spout/spoutlibrary.h>
#include <openspace/properties/stringproperty.h>
#include <openspace/properties/optionproperty.h>
#include <openspace/properties/triggerproperty.h>
#include <modules/spout/spoutwrapper.h>
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

View File

@@ -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 <modules/spout/renderablespherespout.h>
#include <openspace/documentation/documentation.h>
#include <openspace/documentation/verifier.h>
#include <openspace/util/sphere.h>
#include <ghoul/logging/logmanager.h>
#include <ghoul/opengl/texture.h>
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

View File

@@ -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 <modules/base/rendering/renderablesphere.h>
#include <modules/spout/spoutwrapper.h>
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__

View File

@@ -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<Parameters>(dictionary);
std::string identifier;
if (dictionary.hasValue<std::string>(KeyIdentifier)) {
identifier = dictionary.value<std::string>(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

View File

@@ -29,11 +29,7 @@
#include <openspace/rendering/screenspacerenderable.h>
#include <modules/spout/spoutlibrary.h>
#include <openspace/properties/stringproperty.h>
#include <openspace/properties/optionproperty.h>
#include <openspace/properties/triggerproperty.h>
#include <modules/spout/spoutwrapper.h>
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

View File

@@ -25,6 +25,7 @@
#include <modules/spout/spoutmodule.h>
#include <modules/spout/renderableplanespout.h>
#include <modules/spout/renderablespherespout.h>
#include <modules/spout/screenspacespout.h>
#include <openspace/util/factorymanager.h>
#include <ghoul/misc/templatefactory.h>
@@ -46,6 +47,7 @@ void SpoutModule::internalInitialize(const ghoul::Dictionary&) {
FactoryManager::ref().factory<Renderable>();
ghoul_assert(fRenderable, "Renderable factory was not created");
fRenderable->registerClass<RenderablePlaneSpout>("RenderablePlaneSpout");
fRenderable->registerClass<RenderableSphereSpout>("RenderableSphereSpout");
#endif // WIN32
}

View File

@@ -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 <ghoul/fmt.h>
#include <ghoul/logging/logmanager.h>
#include <ghoul/opengl/ghoul_gl.h>
#include <ghoul/opengl/texture.h>
#define SPOUT_NO_GL_INCLUDE
#include <SpoutLibrary.h>
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<unsigned int>(buf);
glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &buf);
_defaultReadFBO = static_cast<unsigned int>(buf);
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &buf);
_defaultDrawFBO = static_cast<unsigned int>(buf);
glGetIntegerv(GL_READ_BUFFER, &buf);
_defaultReadBuffer = static_cast<unsigned int>(buf);
glGetIntegerv(GL_DRAW_BUFFER0, &buf);
_defaultReadBuffer = static_cast<unsigned int>(buf);
saveGLTextureState();
}
void SpoutMain::restoreGLState() {
glBindFramebuffer(GL_FRAMEBUFFER, static_cast<GLuint>(_defaultFBO));
if (_defaultFBO) {
glBindFramebuffer(GL_READ_FRAMEBUFFER, static_cast<GLuint>(_defaultReadFBO));
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, static_cast<GLuint>(_defaultDrawFBO));
glReadBuffer(static_cast<GLenum>(_defaultReadBuffer));
GLenum buf[1];
buf[0] = static_cast<GLenum>(_defaultDrawBuffer[0]);
glDrawBuffers(1, buf);
}
restoreGLTextureState();
}
void SpoutMain::saveGLTextureState() {
GLint buf;
glGetIntegerv(GL_TEXTURE_BINDING_2D, &buf);
_defaultTexture = static_cast<unsigned int>(buf);
}
void SpoutMain::restoreGLTextureState() {
glBindTexture(GL_TEXTURE_2D, static_cast<GLuint>(_defaultTexture));
}
SpoutReceiver::SpoutReceiver() {}
SpoutReceiver::~SpoutReceiver() {}
const std::vector<std::string> &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<GLuint>(*_spoutTexture),
static_cast<GLuint>(GL_TEXTURE_2D),
true
);
if (_onUpdateReceiverCallback) {
const GLuint t = static_cast<GLuint>(*_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<bool(const std::string&)> callback)
{
_onUpdateReceiverNameCallback = std::move(callback);
}
void SpoutReceiver::onUpdateReceiver(std::function<bool(int, int, unsigned int)> callback)
{
_onUpdateReceiverCallback = std::move(callback);
}
void SpoutReceiver::onReleaseReceiver(std::function<void()> callback) {
_onReleaseReceiverCallback = std::move(callback);
}
void SpoutReceiver::onUpdateTexture(std::function<bool(int, int)> callback) {
_onUpdateTextureCallback = std::move(callback);
}
void SpoutReceiver::onReleaseTexture(std::function<void()> callback) {
_onReleaseTextureCallback = std::move(callback);
}
unsigned int SpoutReceiver::spoutTexture() const {
return _spoutTexture ? static_cast<unsigned int>(*_spoutTexture) : 0;
}
bool SpoutReceiver::updateTexture(unsigned int width, unsigned int height) {
if (width != _spoutWidth || height != _spoutHeight) {
releaseTexture();
_spoutTexture = std::make_unique<ghoul::opengl::Texture>(
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<std::string>(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<std::string> receiverList = spoutReceiverList();
_spoutSelection.clearOptions();
_spoutSelection.addOption(0, "");
int idx = 0;
for (int i = 0; i < static_cast<int>(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<int>(_spoutWidth) &&
height == static_cast<int>(_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<bool(const std::string&)> callback) {
_onUpdateSenderNameCallback = std::move(callback);
}
void SpoutSender::onUpdateSenderSize(std::function<bool(int, int)> callback) {
_onUpdateSenderSizeCallback = std::move(callback);
}
void SpoutSender::onUpdateSender(std::function<bool(const std::string&, unsigned int,
unsigned int, int, int)> callback)
{
_onUpdateSenderCallback = std::move(callback);
}
void SpoutSender::onReleaseSender(std::function<void()> 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<std::string>(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

View File

@@ -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 <openspace/properties/optionproperty.h>
#include <openspace/properties/propertyowner.h>
#include <openspace/properties/stringproperty.h>
#include <openspace/properties/triggerproperty.h>
#include <string>
#include <vector>
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<bool(const std::string&)> callback);
void onUpdateReceiver(std::function<bool(int, int, unsigned int)> callback);
void onReleaseReceiver(std::function<void()> callback);
void onUpdateTexture(std::function<bool(int, int)> callback);
void onReleaseTexture(std::function<void()> callback);
const std::vector<std::string>& 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<std::string> _receiverList;
std::unique_ptr<ghoul::opengl::Texture> _spoutTexture;
std::function<bool(const std::string&)> _onUpdateReceiverNameCallback = nullptr;
std::function<bool(int, int, unsigned int)> _onUpdateReceiverCallback = nullptr;
std::function<void()> _onReleaseReceiverCallback = nullptr;
std::function<bool(int, int)> _onUpdateTextureCallback = nullptr;
std::function<void()> _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<bool(const std::string&)> callback);
void onUpdateSenderSize(std::function<bool(int, int)> callback);
void onUpdateSender(std::function<bool(const std::string&, unsigned int,
unsigned int, int, int)> callback);
void onReleaseSender(std::function<void()> callback);
bool isCreated() const;
bool isSending() const;
private:
bool updateSenderStatus();
bool _isErrorMessageDisplayed = false;
bool _isCreated = false;
bool _isSending = false;
std::function<bool(const std::string&)> _onUpdateSenderNameCallback = nullptr;
std::function<bool(int, int)> _onUpdateSenderSizeCallback = nullptr;
std::function<bool(const std::string&, unsigned int,
unsigned int, int, int)> _onUpdateSenderCallback = nullptr;
std::function<void()> _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__

View File

@@ -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.