From 4b793d5344af26a0fca4f6d7ccc7ce0b55d3c935 Mon Sep 17 00:00:00 2001 From: Scott Giese Date: Sat, 2 Feb 2019 19:46:04 -0600 Subject: [PATCH 01/33] Remove deprecated boost/utility. This is enable compatibility with boost 1.69. --- simgear/nasal/cppbind/NasalHash.hxx | 1 + simgear/props/props.hxx | 3 ++- simgear/structure/SGWeakReferenced.hxx | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/simgear/nasal/cppbind/NasalHash.hxx b/simgear/nasal/cppbind/NasalHash.hxx index aac672fd..5019d08b 100644 --- a/simgear/nasal/cppbind/NasalHash.hxx +++ b/simgear/nasal/cppbind/NasalHash.hxx @@ -24,6 +24,7 @@ #include #include +#include namespace nasal { diff --git a/simgear/props/props.hxx b/simgear/props/props.hxx index c94186cb..68d3c03c 100644 --- a/simgear/props/props.hxx +++ b/simgear/props/props.hxx @@ -55,13 +55,14 @@ namespace boost { struct disable_if : public disable_if_c {}; } #else -# include # include +# include # include # include # include #endif + #include #include diff --git a/simgear/structure/SGWeakReferenced.hxx b/simgear/structure/SGWeakReferenced.hxx index 3bb70ffa..27f647b4 100644 --- a/simgear/structure/SGWeakReferenced.hxx +++ b/simgear/structure/SGWeakReferenced.hxx @@ -22,7 +22,7 @@ #include "SGSharedPtr.hxx" #include -#include +#include #ifdef _MSC_VER # pragma warning(push) From dc2f24dbed038beda4e4464d95cac29a9c420f95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Garc=C3=ADa=20Li=C3=B1=C3=A1n?= Date: Mon, 4 Feb 2019 17:59:18 +0100 Subject: [PATCH 02/33] Compositor framework initial commit. --- simgear/scene/CMakeLists.txt | 1 + simgear/scene/material/EffectCullVisitor.cxx | 21 +- simgear/scene/material/EffectCullVisitor.hxx | 4 +- simgear/scene/viewer/CMakeLists.txt | 16 + simgear/scene/viewer/ClusteredForward.cxx | 209 ++++++ simgear/scene/viewer/ClusteredForward.hxx | 40 + simgear/scene/viewer/Compositor.cxx | 313 ++++++++ simgear/scene/viewer/Compositor.hxx | 140 ++++ simgear/scene/viewer/CompositorBuffer.cxx | 213 ++++++ simgear/scene/viewer/CompositorBuffer.hxx | 46 ++ simgear/scene/viewer/CompositorCommon.hxx | 62 ++ simgear/scene/viewer/CompositorPass.cxx | 741 +++++++++++++++++++ simgear/scene/viewer/CompositorPass.hxx | 134 ++++ 13 files changed, 1932 insertions(+), 8 deletions(-) create mode 100644 simgear/scene/viewer/CMakeLists.txt create mode 100644 simgear/scene/viewer/ClusteredForward.cxx create mode 100644 simgear/scene/viewer/ClusteredForward.hxx create mode 100644 simgear/scene/viewer/Compositor.cxx create mode 100644 simgear/scene/viewer/Compositor.hxx create mode 100644 simgear/scene/viewer/CompositorBuffer.cxx create mode 100644 simgear/scene/viewer/CompositorBuffer.hxx create mode 100644 simgear/scene/viewer/CompositorCommon.hxx create mode 100644 simgear/scene/viewer/CompositorPass.cxx create mode 100644 simgear/scene/viewer/CompositorPass.hxx diff --git a/simgear/scene/CMakeLists.txt b/simgear/scene/CMakeLists.txt index ccbd9416..e9bb45a5 100644 --- a/simgear/scene/CMakeLists.txt +++ b/simgear/scene/CMakeLists.txt @@ -9,6 +9,7 @@ foreach( mylibfolder tgdb util tsync + viewer ) add_subdirectory(${mylibfolder}) diff --git a/simgear/scene/material/EffectCullVisitor.cxx b/simgear/scene/material/EffectCullVisitor.cxx index 22d1ab02..a016a455 100644 --- a/simgear/scene/material/EffectCullVisitor.cxx +++ b/simgear/scene/material/EffectCullVisitor.cxx @@ -34,8 +34,9 @@ namespace simgear using osgUtil::CullVisitor; -EffectCullVisitor::EffectCullVisitor(bool collectLights) : - _collectLights(collectLights) +EffectCullVisitor::EffectCullVisitor(bool collectLights, Effect *effectOverride) : + _collectLights(collectLights), + _effectOverride(effectOverride) { } @@ -61,12 +62,18 @@ void EffectCullVisitor::apply(osg::Geode& node) if (_collectLights && ( eg->getNodeMask() & MODELLIGHT_BIT ) ) { _lightList.push_back( eg ); } - Effect* effect = eg->getEffect(); + Effect *effect; + if (_effectOverride) { + effect = _effectOverride; + } else { + effect = eg->getEffect(); + if (!effect) { + CullVisitor::apply(node); + return; + } + } Technique* technique = 0; - if (!effect) { - CullVisitor::apply(node); - return; - } else if (!(technique = effect->chooseTechnique(&getRenderInfo()))) { + if (!(technique = effect->chooseTechnique(&getRenderInfo()))) { return; } // push the node's state. diff --git a/simgear/scene/material/EffectCullVisitor.hxx b/simgear/scene/material/EffectCullVisitor.hxx index ec8f223a..ef93baa3 100644 --- a/simgear/scene/material/EffectCullVisitor.hxx +++ b/simgear/scene/material/EffectCullVisitor.hxx @@ -29,11 +29,12 @@ class Texture2D; namespace simgear { +class Effect; class EffectGeode; class EffectCullVisitor : public osgUtil::CullVisitor { public: - EffectCullVisitor(bool collectLights = false); + EffectCullVisitor(bool collectLights = false, Effect *effectOverride = 0); EffectCullVisitor(const EffectCullVisitor&); virtual osgUtil::CullVisitor* clone() const; using osgUtil::CullVisitor::apply; @@ -48,6 +49,7 @@ private: std::map > _bufferList; std::vector > _lightList; bool _collectLights; + osg::ref_ptr _effectOverride; }; } #endif diff --git a/simgear/scene/viewer/CMakeLists.txt b/simgear/scene/viewer/CMakeLists.txt new file mode 100644 index 00000000..df57469b --- /dev/null +++ b/simgear/scene/viewer/CMakeLists.txt @@ -0,0 +1,16 @@ +set(HEADERS + ClusteredForward.hxx + Compositor.hxx + CompositorCommon.hxx + CompositorBuffer.hxx + CompositorPass.hxx + ) + +set(SOURCES + ClusteredForward.cxx + Compositor.cxx + CompositorBuffer.cxx + CompositorPass.cxx + ) + +simgear_scene_component(viewer scene/viewer "${SOURCES}" "${HEADERS}") diff --git a/simgear/scene/viewer/ClusteredForward.cxx b/simgear/scene/viewer/ClusteredForward.cxx new file mode 100644 index 00000000..9235a71f --- /dev/null +++ b/simgear/scene/viewer/ClusteredForward.cxx @@ -0,0 +1,209 @@ +// Copyright (C) 2018 Fernando García Liñán +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Library General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Library General Public License for more details. +// +// You should have received a copy of the GNU Library General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + +#include "ClusteredForward.hxx" + +#include +#include +#include +#include +#include + +namespace simgear { +namespace compositor { + +///// BEGIN DEBUG +#define DATA_SIZE 24 +const GLfloat LIGHT_DATA[DATA_SIZE] = { + 0.0, 0.0, -10.0, 1.0, 1.0, 0.0, 0.0, 1.0, + 0.0, 0.0, 10.0, 1.0, 0.0, 1.0, 0.0, 1.0, + 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0 +}; + +#define MAX_LIGHT_INDICES 4096 +#define MAX_POINT_LIGHTS 256 + +struct Light { + osg::Vec3 position; + float range; +}; + +#define NUM_LIGHTS 2 +Light LIGHT_LIST[NUM_LIGHTS] = { + {osg::Vec3(0.0f, 0.0f, -10.0f), 10.0f}, + {osg::Vec3(0.0f, 0.0f, 5.0f), 1000.0f} +}; +///// END DEBUG + +ClusteredForwardDrawCallback::ClusteredForwardDrawCallback() : + _initialized(false), + _tile_size(64), + _light_grid(new osg::Image), + _light_indices(new osg::Image), + _light_data(new osg::FloatArray(MAX_POINT_LIGHTS)) +{ +} + +void +ClusteredForwardDrawCallback::operator()(osg::RenderInfo &renderInfo) const +{ + osg::Camera *camera = renderInfo.getCurrentCamera(); + const osg::Viewport *vp = camera->getViewport(); + const int width = vp->width(); + const int height = vp->height(); + + // Round up + int n_htiles = (width + _tile_size - 1) / _tile_size; + int n_vtiles = (height + _tile_size - 1) / _tile_size; + + if (!_initialized) { + // Create and associate the light grid 3D texture + _light_grid->allocateImage(n_htiles, n_vtiles, 1, + GL_RGB_INTEGER, GL_UNSIGNED_SHORT); + _light_grid->setInternalTextureFormat(GL_RGB16UI); + + osg::ref_ptr light_grid_tex = new osg::Texture3D; + light_grid_tex->setResizeNonPowerOfTwoHint(false); + light_grid_tex->setWrap(osg::Texture3D::WRAP_R, osg::Texture3D::CLAMP_TO_BORDER); + light_grid_tex->setWrap(osg::Texture3D::WRAP_S, osg::Texture3D::CLAMP_TO_BORDER); + light_grid_tex->setWrap(osg::Texture3D::WRAP_T, osg::Texture3D::CLAMP_TO_BORDER); + light_grid_tex->setFilter(osg::Texture3D::MIN_FILTER, osg::Texture3D::NEAREST); + light_grid_tex->setFilter(osg::Texture3D::MAG_FILTER, osg::Texture3D::NEAREST); + light_grid_tex->setImage(0, _light_grid.get()); + + camera->getOrCreateStateSet()->setTextureAttributeAndModes( + 10, light_grid_tex.get(), osg::StateAttribute::ON); + + // Create and associate the light indices TBO + _light_indices->allocateImage(4096, 1, 1, GL_RED_INTEGER, GL_UNSIGNED_SHORT); + + osg::ref_ptr light_indices_tbo = + new osg::TextureBuffer; + light_indices_tbo->setInternalFormat(GL_R16UI); + light_indices_tbo->setImage(_light_indices.get()); + + camera->getOrCreateStateSet()->setTextureAttribute( + 11, light_indices_tbo.get()); + + // Create and associate the light data UBO + osg::ref_ptr light_data_ubo = + new osg::UniformBufferObject; + _light_data->setBufferObject(light_data_ubo.get()); + + osg::ref_ptr light_data_ubb = + new osg::UniformBufferBinding(0, light_data_ubo.get(), + 0, MAX_POINT_LIGHTS * 8 * sizeof(GLfloat)); + light_data_ubb->setDataVariance(osg::Object::DYNAMIC); + + camera->getOrCreateStateSet()->setAttribute( + light_data_ubb.get(), osg::StateAttribute::ON); + + _initialized = true; + } + + std::vector subfrustums; + const osg::Matrix &view_matrix = camera->getViewMatrix(); + const osg::Matrix &proj_matrix = camera->getProjectionMatrix(); + osg::Matrix view_proj_inverse = osg::Matrix::inverse(view_matrix * proj_matrix); + + double x_step = (_tile_size / width) * 2.0; + double y_step = (_tile_size / height) * 2.0; + for (int y = 0; y < n_vtiles; ++y) { + for (int x = 0; x < n_htiles; ++x) { + // Create the subfrustum in clip space + double x_min = -1.0 + x_step * x; double x_max = x_min + x_step; + double y_min = -1.0 + y_step * y; double y_max = y_min + y_step; + double z_min = 1.0; double z_max = -1.0; + osg::BoundingBox subfrustum_bb( + x_min, y_min, z_min, x_max, y_max, z_max); + osg::Polytope subfrustum; + subfrustum.setToBoundingBox(subfrustum_bb); + + // Transform it to world space + subfrustum.transformProvidingInverse(view_proj_inverse); + + subfrustums.push_back(subfrustum); + } + } + + GLushort *grid_data = reinterpret_cast + (_light_grid->data()); + GLushort *index_data = reinterpret_cast + (_light_indices->data()); + + GLushort global_light_count = 0; + for (size_t i = 0; i < subfrustums.size(); ++i) { + GLushort start_offset = global_light_count; + GLushort local_light_count = 0; + + for (GLushort light_list_index = 0; + light_list_index < NUM_LIGHTS; + ++light_list_index) { + const Light &light = LIGHT_LIST[light_list_index]; + osg::BoundingSphere bs(light.position, light.range); + + if (subfrustums[i].contains(bs)) { + index_data[global_light_count] = light_list_index; + ++local_light_count; + ++global_light_count; + } + } + grid_data[i * 3 + 0] = start_offset; + grid_data[i * 3 + 1] = local_light_count; + grid_data[i * 3 + 2] = 0; + } + + _light_grid->dirty(); + _light_indices->dirty(); + + // Upload light data + for (int i = 0; i < DATA_SIZE; ++i) { + (*_light_data)[i] = LIGHT_DATA[i]; + } + + // DEBUG + /* + if (!_debug) { + for (int y = 0; y < num_vtiles; ++y) { + for (int x = 0; x < num_htiles; ++x) { + std::cout << grid_data[(y * num_htiles + x) * 3 + 0] << "," + << grid_data[(y * num_htiles + x) * 3 + 1] << " "; + } + std::cout << std::endl; + } + std::cout << "\n\n"; + + for (int i = 0; i < num_vtiles * num_htiles; ++i) { + std::cout << index_data[i] << " "; + } + std::cout << "\n"; + _debug = true; + } + */ +/* + for (int y = 0; y < num_vtiles; ++y) { + for (int x = 0; x < num_htiles; ++x) { + data[(y * num_htiles + x) * 3 + 0] = (unsigned short)x; + data[(y * num_htiles + x) * 3 + 1] = (unsigned short)y; + data[(y * num_htiles + x) * 3 + 2] = 0; + } + } + _light_grid->dirty(); +*/ +} + +} // namespace compositor +} // namespace simgear diff --git a/simgear/scene/viewer/ClusteredForward.hxx b/simgear/scene/viewer/ClusteredForward.hxx new file mode 100644 index 00000000..8afa47b0 --- /dev/null +++ b/simgear/scene/viewer/ClusteredForward.hxx @@ -0,0 +1,40 @@ +// Copyright (C) 2018 Fernando García Liñán +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Library General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Library General Public License for more details. +// +// You should have received a copy of the GNU Library General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + +#ifndef SG_CLUSTERED_FORWARD_HXX +#define SG_CLUSTERED_FORWARD_HXX + +#include + +namespace simgear { +namespace compositor { + +class ClusteredForwardDrawCallback : public osg::Camera::DrawCallback { +public: + ClusteredForwardDrawCallback(); + virtual void operator()(osg::RenderInfo &renderInfo) const; +protected: + mutable bool _initialized; + int _tile_size; + osg::ref_ptr _light_grid; + osg::ref_ptr _light_indices; + osg::ref_ptr _light_data; +}; + +} // namespace compositor +} // namespace simgear + +#endif /* SG_CLUSTERED_FORWARD_HXX */ diff --git a/simgear/scene/viewer/Compositor.cxx b/simgear/scene/viewer/Compositor.cxx new file mode 100644 index 00000000..8058d0d3 --- /dev/null +++ b/simgear/scene/viewer/Compositor.cxx @@ -0,0 +1,313 @@ +// Copyright (C) 2018 Fernando García Liñán +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Library General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Library General Public License for more details. +// +// You should have received a copy of the GNU Library General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + +#include "Compositor.hxx" + +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace simgear { +namespace compositor { + +Compositor * +Compositor::create(osg::View *view, + osg::GraphicsContext *gc, + osg::Viewport *viewport, + const SGPropertyNode *property_list) +{ + osg::ref_ptr compositor = new Compositor(view, gc, viewport); + compositor->_name = property_list->getStringValue("name"); + + // Read all buffers first so passes can use them + PropertyList p_buffers = property_list->getChildren("buffer"); + for (auto const &p_buffer : p_buffers) { + const std::string &buffer_name = p_buffer->getStringValue("name"); + if (buffer_name.empty()) { + SG_LOG(SG_INPUT, SG_ALERT, "Compositor::build: Buffer requires " + "a name to be available to passes. Skipping..."); + continue; + } + Buffer *buffer = buildBuffer(compositor.get(), p_buffer); + if (buffer) + compositor->addBuffer(buffer_name, buffer); + } + // Read passes + PropertyList p_passes = property_list->getChildren("pass"); + for (auto const &p_pass : p_passes) { + Pass *pass = buildPass(compositor.get(), p_pass); + if (pass) + compositor->addPass(pass); + } + + return compositor.release(); +} + +Compositor * +Compositor::create(osg::View *view, + osg::GraphicsContext *gc, + osg::Viewport *viewport, + const std::string &name) +{ + std::string filename(name); + filename += ".xml"; + std::string abs_filename = SGModelLib::findDataFile(filename); + if (abs_filename.empty()) { + SG_LOG(SG_INPUT, SG_ALERT, "Compositor::build: Could not find file '" + << filename << "'"); + return 0; + } + + SGPropertyNode_ptr property_list = new SGPropertyNode; + try { + readProperties(abs_filename, property_list.ptr(), 0, true); + } catch (sg_io_exception &e) { + SG_LOG(SG_INPUT, SG_ALERT, "Compositor::build: Failed to parse file '" + << abs_filename << "'. " << e.getFormattedMessage()); + return 0; + } + + return create(view, gc, viewport, property_list); +} + +Compositor::Compositor(osg::View *view, + osg::GraphicsContext *gc, + osg::Viewport *viewport) : + _view(view), + _gc(gc), + _viewport(viewport), + _uniforms{ + new osg::Uniform("fg_ViewportSize", osg::Vec2f()), + new osg::Uniform("fg_ViewMatrix", osg::Matrixf()), + new osg::Uniform("fg_ViewMatrixInverse", osg::Matrixf()), + new osg::Uniform("fg_ProjectionMatrix", osg::Matrixf()), + new osg::Uniform("fg_ProjectionMatrixInverse", osg::Matrixf()), + new osg::Uniform("fg_CameraPositionCart", osg::Vec3f()), + new osg::Uniform("fg_CameraPositionGeod", osg::Vec3f()) + } +{ +} + +Compositor::~Compositor() +{ +} + +void +Compositor::update(const osg::Matrix &view_matrix, + const osg::Matrix &proj_matrix) +{ + for (auto &pass : _passes) { + if (pass->update_callback.valid()) + pass->update_callback->updatePass(*pass.get(), view_matrix, proj_matrix); + } + + // Update uniforms + osg::Matrixd view_inverse = osg::Matrix::inverse(view_matrix); + osg::Vec4d camera_pos = osg::Vec4(0.0, 0.0, 0.0, 1.0) * view_inverse; + SGGeod camera_pos_geod = SGGeod::fromCart( + SGVec3d(camera_pos.x(), camera_pos.y(), camera_pos.z())); + + for (int i = 0; i < TOTAL_BUILTIN_UNIFORMS; ++i) { + osg::ref_ptr u = _uniforms[i]; + switch (i) { + case VIEWPORT_SIZE: + u->set(osg::Vec2f(_viewport->width(), _viewport->height())); + break; + case VIEW_MATRIX: + u->set(view_matrix); + break; + case VIEW_MATRIX_INV: + u->set(view_inverse); + break; + case PROJECTION_MATRIX: + u->set(proj_matrix); + break; + case PROJECTION_MATRIX_INV: + u->set(osg::Matrix::inverse(proj_matrix)); + break; + case CAMERA_POSITION_CART: + u->set(osg::Vec3f(camera_pos.x(), camera_pos.y(), camera_pos.z())); + break; + case CAMERA_POSITION_GEOD: + u->set(osg::Vec3f(camera_pos_geod.getLongitudeRad(), + camera_pos_geod.getLatitudeRad(), + camera_pos_geod.getElevationM())); + break; + default: + // Unknown uniform + break; + } + } +} + +void +Compositor::resized() +{ + // Cameras attached directly to the framebuffer were already resized by + // osg::GraphicsContext::resizedImplementation(). However, RTT cameras were + // ignored. Here we resize RTT cameras that need to match the physical + // viewport size. + for (const auto &pass : _passes) { + osg::Camera *camera = pass->camera; + if (!camera->isRenderToTextureCamera() || + pass->viewport_width_scale == 0.0f || + pass->viewport_height_scale == 0.0f) + continue; + + // Resize both the viewport and its texture attachments + camera->resize(pass->viewport_width_scale * _viewport->width(), + pass->viewport_height_scale * _viewport->height()); + } +} + +void +Compositor::setCameraCullMasks(osg::Node::NodeMask nm) +{ + for (const auto &pass : _passes) { + osg::Camera *camera = pass->camera; + osg::Node::NodeMask pass_cm = nm; + // Disable traversal of the scene graph if the pass isn't enabled + if (!pass->isEnabled()) + pass_cm &= 0x0; + else + pass_cm &= pass->cull_mask; + + camera->setCullMask(pass_cm); + camera->setCullMaskLeft(pass_cm); + camera->setCullMaskRight(pass_cm); + } +} + +bool +Compositor::computeIntersection( + const osg::Vec2d& windowPos, + osgUtil::LineSegmentIntersector::Intersections& intersections) +{ + using osgUtil::Intersector; + using osgUtil::LineSegmentIntersector; + + osg::Camera *camera = getPass(0)->camera; + const osg::Viewport* viewport = camera->getViewport(); + SGRect viewportRect(viewport->x(), viewport->y(), + viewport->x() + viewport->width() - 1.0, + viewport->y() + viewport->height()- 1.0); + + double epsilon = 0.5; + if (!viewportRect.contains(windowPos.x(), windowPos.y(), epsilon)) + return false; + + osg::Vec4d start(windowPos.x(), windowPos.y(), 0.0, 1.0); + osg::Vec4d end(windowPos.x(), windowPos.y(), 1.0, 1.0); + osg::Matrix windowMat = viewport->computeWindowMatrix(); + osg::Matrix startPtMat = osg::Matrix::inverse(camera->getProjectionMatrix() + * windowMat); + osg::Matrix endPtMat = startPtMat; // no far camera + + start = start * startPtMat; + start /= start.w(); + end = end * endPtMat; + end /= end.w(); + osg::ref_ptr picker + = new LineSegmentIntersector(Intersector::VIEW, + osg::Vec3d(start.x(), start.y(), start.z()), + osg::Vec3d(end.x(), end.y(), end.z())); + osgUtil::IntersectionVisitor iv(picker.get()); + iv.setTraversalMask( simgear::PICK_BIT ); + + const_cast(camera)->accept(iv); + if (picker->containsIntersections()) { + intersections = picker->getIntersections(); + return true; + } + + return false; +} + +void +Compositor::addBuffer(const std::string &name, Buffer *buffer) +{ + _buffers[name] = buffer; +} + +void +Compositor::addPass(Pass *pass) +{ + if (!_view) { + SG_LOG(SG_GENERAL, SG_ALERT, "Compositor::addPass: Couldn't add camera " + "as a slave to the view. View doesn't exist!"); + return; + } + + _view->addSlave(pass->camera, pass->useMastersSceneData); + + // Install the Effect cull visitor + osgViewer::Renderer* renderer + = static_cast(pass->camera->getRenderer()); + for (int i = 0; i < 2; ++i) { + osgUtil::SceneView* sceneView = renderer->getSceneView(i); + + osg::ref_ptr identifier; + identifier = sceneView->getCullVisitor()->getIdentifier(); + + sceneView->setCullVisitor( + new EffectCullVisitor(false, pass->effect_override)); + sceneView->getCullVisitor()->setIdentifier(identifier.get()); + + identifier = sceneView->getCullVisitorLeft()->getIdentifier(); + sceneView->setCullVisitorLeft(sceneView->getCullVisitor()->clone()); + sceneView->getCullVisitorLeft()->setIdentifier(identifier.get()); + + identifier = sceneView->getCullVisitorRight()->getIdentifier(); + sceneView->setCullVisitorRight(sceneView->getCullVisitor()->clone()); + sceneView->getCullVisitorRight()->setIdentifier(identifier.get()); + } + + _passes.push_back(pass); +} + +Buffer * +Compositor::getBuffer(const std::string &name) const +{ + auto it = _buffers.find(name); + if (it == _buffers.end()) + return 0; + return it->second.get(); +} + +Pass * +Compositor::getPass(const std::string &name) const +{ + auto it = std::find_if(_passes.begin(), _passes.end(), + [&name](const osg::ref_ptr &p) { + return p->name == name; + }); + if (it == _passes.end()) + return 0; + return (*it); +} + +} // namespace compositor +} // namespace simgear diff --git a/simgear/scene/viewer/Compositor.hxx b/simgear/scene/viewer/Compositor.hxx new file mode 100644 index 00000000..25744efb --- /dev/null +++ b/simgear/scene/viewer/Compositor.hxx @@ -0,0 +1,140 @@ +// Copyright (C) 2018 Fernando García Liñán +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Library General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Library General Public License for more details. +// +// You should have received a copy of the GNU Library General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + +#ifndef SG_COMPOSITOR_HXX +#define SG_COMPOSITOR_HXX + +#include +#include + +// For osgUtil::LineSegmentIntersector::Intersections, which is a typedef. +#include + +#include "CompositorBuffer.hxx" +#include "CompositorPass.hxx" + +class SGPropertyNode; + +namespace simgear { +namespace compositor { + +/** + * A Compositor manages the rendering pipeline of a single physical camera, + * usually via a property tree interface. + * + * The building blocks that define a Compositor are: + * - Buffers. They represent a zone of GPU memory. This is implemented in the + * form of an OpenGL texture, but any type of information can be stored + * (which can be useful in compute shaders for example). + * - Passes. They represent render operations. They can get buffers as input + * and they can output to other buffers. They are also integrated with the + * Effects framework, so the OpenGL internal state is configurable per pass. + */ +class Compositor : public osg::Referenced { +public: + enum BuiltinUniform { + VIEWPORT_SIZE = 0, + VIEW_MATRIX, + VIEW_MATRIX_INV, + PROJECTION_MATRIX, + PROJECTION_MATRIX_INV, + CAMERA_POSITION_CART, + CAMERA_POSITION_GEOD, + TOTAL_BUILTIN_UNIFORMS + }; + + Compositor(osg::View *view, + osg::GraphicsContext *gc, + osg::Viewport *viewport); + ~Compositor(); + + /** + * \brief Create a Compositor from a property tree. + * + * @param view The View where the passes will be added as slaves. + * @param gc The context where the internal osg::Cameras will draw on. + * @param viewport The viewport position and size inside the window. + * @param property_list A valid property list that describes the Compositor. + * @return A Compositor or a null pointer if there was an error. + */ + static Compositor *create(osg::View *view, + osg::GraphicsContext *gc, + osg::Viewport *viewport, + const SGPropertyNode *property_list); + /** + * \overload + * \brief Create a Compositor from a file. + * + * @param name Name of the compositor. The function will search for a file + * named .xml in $FG_ROOT. + */ + static Compositor *create(osg::View *view, + osg::GraphicsContext *gc, + osg::Viewport *viewport, + const std::string &name); + + void update(const osg::Matrix &view_matrix, + const osg::Matrix &proj_matrix); + + void resized(); + + void setCameraCullMasks(osg::Node::NodeMask nm); + + bool computeIntersection( + const osg::Vec2d& windowPos, + osgUtil::LineSegmentIntersector::Intersections& intersections); + + const osg::GraphicsContext *getGraphicsContext() const { return _gc; } + + const osg::Viewport *getViewport() const { return _viewport; } + + typedef std::array< + osg::ref_ptr, + TOTAL_BUILTIN_UNIFORMS> BuiltinUniforms; + const BuiltinUniforms &getUniforms() const { return _uniforms; } + + void addBuffer(const std::string &name, Buffer *buffer); + void addPass(Pass *pass); + + void setName(const std::string &name) { _name = name; } + const std::string &getName() const { return _name; } + + typedef std::unordered_map> BufferMap; + const BufferMap & getBufferMap() const { return _buffers; } + Buffer * getBuffer(const std::string &name) const; + + typedef std::vector> PassList; + const PassList & getPassList() const { return _passes; } + unsigned int getNumPasses() const { return _passes.size(); } + Pass * getPass(size_t index) const { return _passes[index]; } + Pass * getPass(const std::string &name) const; + +protected: + friend class PassBuilder; + + osg::View *_view; + osg::GraphicsContext *_gc; + osg::ref_ptr _viewport; + std::string _name; + BufferMap _buffers; + PassList _passes; + BuiltinUniforms _uniforms; +}; + +} // namespace compositor +} // namespace simgear + +#endif /* SG_COMPOSITOR_HXX */ diff --git a/simgear/scene/viewer/CompositorBuffer.cxx b/simgear/scene/viewer/CompositorBuffer.cxx new file mode 100644 index 00000000..b52b5530 --- /dev/null +++ b/simgear/scene/viewer/CompositorBuffer.cxx @@ -0,0 +1,213 @@ +// Copyright (C) 2018 Fernando García Liñán +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Library General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Library General Public License for more details. +// +// You should have received a copy of the GNU Library General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + +#include "CompositorBuffer.hxx" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "Compositor.hxx" +#include "CompositorCommon.hxx" + +namespace simgear { +namespace compositor { + +struct BufferFormat { + GLint internal_format; + GLenum source_format; + GLenum source_type; +}; + +PropStringMap buffer_format_map { + {"rgb8", {GL_RGB8, GL_RGBA, GL_UNSIGNED_BYTE}}, + {"rgba8", {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE}}, + {"rgb16f", {GL_RGB16F, GL_RGBA, GL_FLOAT}}, + {"rgb32f", {GL_RGB32F, GL_RGBA, GL_FLOAT}}, + {"rgba16f", {GL_RGBA16F, GL_RGBA, GL_FLOAT}}, + {"rgba32f", {GL_RGBA32F, GL_RGBA, GL_FLOAT}}, + {"r32f", {GL_R32F, GL_RED, GL_FLOAT}}, + {"rg32f", {GL_RG32F, GL_RG, GL_FLOAT}}, + {"depth16", {GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}}, + {"depth24", {GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT, GL_FLOAT}}, + {"depth32", {GL_DEPTH_COMPONENT32, GL_DEPTH_COMPONENT, GL_FLOAT}}, + {"depth32f", {GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT}}, + {"depth-stencil", {GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_FLOAT}} +}; + +PropStringMap wrap_mode_map = { + {"clamp", osg::Texture::CLAMP}, + {"clamp-to-edge", osg::Texture::CLAMP_TO_EDGE}, + {"clamp-to-border", osg::Texture::CLAMP_TO_BORDER}, + {"repeat", osg::Texture::REPEAT}, + {"mirror", osg::Texture::MIRROR} +}; + +PropStringMap filter_mode_map = { + {"linear", osg::Texture::LINEAR}, + {"linear-mipmap-linear", osg::Texture::LINEAR_MIPMAP_LINEAR}, + {"linear-mipmap-nearest", osg::Texture::LINEAR_MIPMAP_NEAREST}, + {"nearest", osg::Texture::NEAREST}, + {"nearest-mipmap-linear", osg::Texture::NEAREST_MIPMAP_LINEAR}, + {"nearest-mipmap-nearest", osg::Texture::NEAREST_MIPMAP_NEAREST} +}; + +PropStringMap shadow_texture_mode_map = { + {"luminance", osg::Texture::LUMINANCE}, + {"intensity", osg::Texture::INTENSITY}, + {"alpha", osg::Texture::ALPHA} +}; + +PropStringMap shadow_compare_func_map = { + {"never", osg::Texture::NEVER}, + {"less", osg::Texture::LESS}, + {"equal", osg::Texture::EQUAL}, + {"lequal", osg::Texture::LEQUAL}, + {"greater", osg::Texture::GREATER}, + {"notequal", osg::Texture::NOTEQUAL}, + {"gequal", osg::Texture::GEQUAL}, + {"always", osg::Texture::ALWAYS} +}; + +Buffer * +buildBuffer(Compositor *compositor, const SGPropertyNode *node) +{ + std::string type = node->getStringValue("type"); + if (type.empty()) { + SG_LOG(SG_INPUT, SG_ALERT, "buildBuffer: No type specified"); + return 0; + } + + osg::ref_ptr buffer = new Buffer; + osg::Texture *texture; + + int width; + if (node->getStringValue("width") == std::string("screen")) { + float w_scale = node->getFloatValue("screen-width-scale", 1.0f); + buffer->width_scale = w_scale; + width = w_scale * compositor->getViewport()->width(); + } else { + width = node->getIntValue("width"); + } + int height; + if (node->getStringValue("height") == std::string("screen")) { + float h_scale = node->getFloatValue("screen-height-scale", 1.0f); + buffer->height_scale = h_scale; + height = h_scale * compositor->getViewport()->height(); + } else { + height = node->getIntValue("height"); + } + int depth = node->getIntValue("depth"); + if (type == "1d") { + osg::Texture1D *tex1D = new osg::Texture1D; + tex1D->setTextureWidth(width); + texture = tex1D; + } else if (type == "2d") { + osg::Texture2D *tex2D = new osg::Texture2D; + tex2D->setTextureSize(width, height); + texture = tex2D; + } else if (type == "2d-array") { + osg::Texture2DArray *tex2D_array = new osg::Texture2DArray; + tex2D_array->setTextureSize(width, height, depth); + texture = tex2D_array; + } else if (type == "2d-multisample") { + osg::Texture2DMultisample *tex2DMS = new osg::Texture2DMultisample; + tex2DMS->setTextureSize(width, height); + tex2DMS->setNumSamples(node->getIntValue("num-samples", 0)); + texture = tex2DMS; + } else if (type == "3d") { + osg::Texture3D *tex3D = new osg::Texture3D; + tex3D->setTextureSize(width, height, depth); + texture = tex3D; + } else if (type == "rect") { + osg::TextureRectangle *tex_rect = new osg::TextureRectangle; + tex_rect->setTextureSize(width, height); + texture = tex_rect; + } else if (type == "cubemap") { + osg::TextureCubeMap *tex_cubemap = new osg::TextureCubeMap; + tex_cubemap->setTextureSize(width, height); + texture = tex_cubemap; + } else { + SG_LOG(SG_INPUT, SG_ALERT, "Unknown texture type '" << type << "'"); + return 0; + } + buffer->texture = texture; + + bool resize_npot = node->getBoolValue("resize-npot", false); + texture->setResizeNonPowerOfTwoHint(resize_npot); + + BufferFormat format; + if (findPropString(node, "format", format, buffer_format_map)) { + texture->setInternalFormat(format.internal_format); + texture->setSourceFormat(format.source_format); + texture->setSourceType(format.source_type); + } else { + texture->setInternalFormat(GL_RGBA); + SG_LOG(SG_INPUT, SG_WARN, "Unknown buffer format specified, using RGBA"); + } + + osg::Texture::FilterMode filter_mode = osg::Texture::LINEAR; + findPropString(node, "min-filter", filter_mode, filter_mode_map); + texture->setFilter(osg::Texture::MIN_FILTER, filter_mode); + findPropString(node, "mag-filter", filter_mode, filter_mode_map); + texture->setFilter(osg::Texture::MAG_FILTER, filter_mode); + + osg::Texture::WrapMode wrap_mode = osg::Texture::CLAMP_TO_BORDER; + findPropString(node, "wrap-s", wrap_mode, wrap_mode_map); + texture->setWrap(osg::Texture::WRAP_S, wrap_mode); + findPropString(node, "wrap-t", wrap_mode, wrap_mode_map); + texture->setWrap(osg::Texture::WRAP_T, wrap_mode); + findPropString(node, "wrap-r", wrap_mode, wrap_mode_map); + texture->setWrap(osg::Texture::WRAP_R, wrap_mode); + + float anis = node->getFloatValue("anisotropy", 1.0f); + texture->setMaxAnisotropy(anis); + + osg::Vec4f border_color(0.0f, 0.0f, 0.0f, 0.0f); + const SGPropertyNode *p_border_color = node->getChild("border-color"); + if (p_border_color) + border_color = toOsg(p_border_color->getValue()); + texture->setBorderColor(border_color); + + bool shadow_comparison = node->getBoolValue("shadow-comparison", false); + texture->setShadowComparison(shadow_comparison); + if (shadow_comparison) { + osg::Texture::ShadowTextureMode shadow_texture_mode = + osg::Texture::LUMINANCE; + findPropString(node, "shadow-texture-mode", + shadow_texture_mode, shadow_texture_mode_map); + texture->setShadowTextureMode(shadow_texture_mode); + + osg::Texture::ShadowCompareFunc shadow_compare_func = + osg::Texture::LEQUAL; + findPropString(node, "shadow-compare-func", + shadow_compare_func, shadow_compare_func_map); + texture->setShadowCompareFunc(shadow_compare_func); + } + + return buffer.release(); +} + +} // namespace compositor +} // namespace simgear diff --git a/simgear/scene/viewer/CompositorBuffer.hxx b/simgear/scene/viewer/CompositorBuffer.hxx new file mode 100644 index 00000000..df0a92a4 --- /dev/null +++ b/simgear/scene/viewer/CompositorBuffer.hxx @@ -0,0 +1,46 @@ +// Copyright (C) 2018 Fernando García Liñán +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Library General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Library General Public License for more details. +// +// You should have received a copy of the GNU Library General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + +#ifndef SG_COMPOSITOR_BUFFER_HXX +#define SG_COMPOSITOR_BUFFER_HXX + +#include + +class SGPropertyNode; + +namespace simgear { +namespace compositor { + +class Compositor; + +struct Buffer : public osg::Referenced { + Buffer() : width_scale(0.0f), height_scale(0.0f) {} + + osg::ref_ptr texture; + + /** + * The amount to multiply the size of the default framebuffer. + * A factor of 0.0 means that the buffer has a fixed size. + */ + float width_scale, height_scale; +}; + +Buffer *buildBuffer(Compositor *compositor, const SGPropertyNode *node); + +} // namespace compositor +} // namespace simgear + +#endif /* SG_COMPOSITOR_BUFFER_HXX */ diff --git a/simgear/scene/viewer/CompositorCommon.hxx b/simgear/scene/viewer/CompositorCommon.hxx new file mode 100644 index 00000000..17d519ae --- /dev/null +++ b/simgear/scene/viewer/CompositorCommon.hxx @@ -0,0 +1,62 @@ +// Copyright (C) 2018 Fernando García Liñán +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Library General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Library General Public License for more details. +// +// You should have received a copy of the GNU Library General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + +#ifndef SG_COMPOSITOR_COMMON_HXX +#define SG_COMPOSITOR_COMMON_HXX + +#include + +namespace simgear { +namespace compositor { + +/** + * Lookup table that ties a string property value to a type that cannot be + * represented in the property tree. Useful for OSG or OpenGL enums. + */ +template +using PropStringMap = const std::unordered_map; + +template +bool findPropString(const std::string &str, + T &value, + const PropStringMap &map) +{ + auto itr = map.find(str); + if (itr == map.end()) + return false; + + value = itr->second; + return true; +} + +template +bool findPropString(const SGPropertyNode *parent, + const std::string &child_name, + T &value, + const PropStringMap &map) +{ + const SGPropertyNode *child = parent->getNode(child_name); + if (child) { + if (findPropString(child->getStringValue(), value, map)) + return true; + } + return false; +} + +} // namespace compositor +} // namespace simgear + +#endif /* SG_COMPOSITOR_COMMON_HXX */ diff --git a/simgear/scene/viewer/CompositorPass.cxx b/simgear/scene/viewer/CompositorPass.cxx new file mode 100644 index 00000000..d62a8a90 --- /dev/null +++ b/simgear/scene/viewer/CompositorPass.cxx @@ -0,0 +1,741 @@ +// Copyright (C) 2018 Fernando García Liñán +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Library General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Library General Public License for more details. +// +// You should have received a copy of the GNU Library General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + +#include "CompositorPass.hxx" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "ClusteredForward.hxx" +#include "Compositor.hxx" +#include "CompositorCommon.hxx" + +namespace simgear { +namespace compositor { + +PropStringMap buffer_component_map = { + {"color", osg::Camera::COLOR_BUFFER}, + {"color0", osg::Camera::COLOR_BUFFER0}, + {"color1", osg::Camera::COLOR_BUFFER1}, + {"color2", osg::Camera::COLOR_BUFFER2}, + {"color3", osg::Camera::COLOR_BUFFER3}, + {"color4", osg::Camera::COLOR_BUFFER4}, + {"color5", osg::Camera::COLOR_BUFFER5}, + {"color6", osg::Camera::COLOR_BUFFER6}, + {"color7", osg::Camera::COLOR_BUFFER7}, + {"depth", osg::Camera::DEPTH_BUFFER}, + {"stencil", osg::Camera::STENCIL_BUFFER}, + {"packed-depth-stencil", osg::Camera::PACKED_DEPTH_STENCIL_BUFFER} +}; + +Pass * +PassBuilder::build(Compositor *compositor, const SGPropertyNode *root) +{ + // The pass index matches its render order + int render_order = root->getIndex(); + + osg::ref_ptr pass = new Pass; + pass->name = root->getStringValue("name"); + if (pass->name.empty()) { + SG_LOG(SG_INPUT, SG_WARN, "PassBuilder::build: Pass " << render_order + << " has no name. It won't be addressable by name!"); + } + pass->type = root->getStringValue("type"); + + const SGPropertyNode *condition = root->getChild("condition"); + if (condition) + pass->condition = sgReadCondition(getPropertyRoot(), condition); + + std::string eff_override_file = root->getStringValue("effect-override"); + if (!eff_override_file.empty()) + pass->effect_override = makeEffect(eff_override_file, true, 0); + + pass->cull_mask = std::stoul(root->getStringValue("cull-mask", "0xffffff"), + nullptr, 0); + + osg::Camera *camera = new Camera; + pass->camera = camera; + + camera->setName(pass->name); + camera->setGraphicsContext(compositor->_gc); + // Even though this camera will be added as a slave to the view, it will + // always be updated manually in Compositor::update() + camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF); + // Same with the projection matrix + camera->setProjectionResizePolicy(osg::Camera::FIXED); + camera->setComputeNearFarMode(osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR); + + osg::Vec4f clear_color(0.0f, 0.0f, 0.0f, 0.0f); + const SGPropertyNode *p_clear_color = root->getChild("clear-color"); + if (p_clear_color) + clear_color = toOsg(p_clear_color->getValue()); + camera->setClearColor(clear_color); + osg::Vec4f clear_accum(0.0f, 0.0f, 0.0f, 0.0f); + const SGPropertyNode *p_clear_accum = root->getChild("clear-accum"); + if (p_clear_accum) + clear_accum = toOsg(p_clear_accum->getValue()); + camera->setClearAccum(clear_accum); + camera->setClearDepth(root->getFloatValue("clear-depth", 1.0f)); + camera->setClearStencil(root->getIntValue("clear-stencil", 0)); + + GLbitfield clear_mask = 0; + if (root->getBoolValue("clear-color-bit", true)) + clear_mask |= GL_COLOR_BUFFER_BIT; + if (root->getBoolValue("clear-accum-bit", false)) + clear_mask |= GL_ACCUM_BUFFER_BIT; + if (root->getBoolValue("clear-depth-bit", true)) + clear_mask |= GL_DEPTH_BUFFER_BIT; + if (root->getBoolValue("clear-stencil-bit", false)) + clear_mask |= GL_STENCIL_BUFFER_BIT; + // Default clear mask is GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, as in OSG + camera->setClearMask(clear_mask); + + PropertyList p_bindings = root->getChildren("binding"); + for (auto const &p_binding : p_bindings) { + try { + std::string buffer_name = p_binding->getStringValue("buffer"); + if (buffer_name.empty()) + throw sg_exception("No buffer specified"); + + Buffer *buffer = compositor->getBuffer(buffer_name); + if (!buffer) + throw sg_exception(std::string("Unknown buffer '") + + buffer_name + "'"); + + osg::Texture *texture = buffer->texture; + + int unit = p_binding->getIntValue("unit", -1); + if (unit < 0) + throw sg_exception("No texture unit specified"); + + // Make the texture available to every child of the pass, overriding + // existing units + camera->getOrCreateStateSet()->setTextureAttributeAndModes( + unit, + texture, + osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + } catch (sg_exception &e) { + SG_LOG(SG_INPUT, SG_ALERT, "PassBuilder::build: Skipping binding " + << p_binding->getIndex() << " in pass " << render_order + << ": " << e.what()); + } + } + + PropertyList p_attachments = root->getChildren("attachment"); + if (p_attachments.empty()) { + // If there are no attachments, assume the pass is rendering + // directly to the screen + + camera->setRenderOrder(osg::Camera::NESTED_RENDER, render_order * 10); + // OSG cameras use the framebuffer by default, but it is stated + // explicitly anyway + camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER); + + camera->setDrawBuffer(GL_BACK); + camera->setReadBuffer(GL_BACK); + + // Use the physical viewport. We can't let the user choose the viewport + // size because some parts of the window might not be ours. + camera->setViewport(compositor->_viewport); + } else { + // This is a RTT camera + + camera->setRenderOrder(osg::Camera::PRE_RENDER, render_order * 10); + camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); + + bool viewport_absolute = false; + // The index of the attachment to be used as the size of the viewport. + // The one with index 0 is used by default. + int viewport_attachment = 0; + const SGPropertyNode *p_viewport = root->getChild("viewport"); + if (p_viewport) { + // The user has manually specified a viewport size + viewport_absolute = p_viewport->getBoolValue("absolute", false); + if (viewport_absolute) { + camera->setViewport(p_viewport->getIntValue("x"), + p_viewport->getIntValue("y"), + p_viewport->getIntValue("width"), + p_viewport->getIntValue("height")); + } + viewport_attachment = p_viewport->getIntValue("use-attachment", 0); + if (!root->getChild("attachment", viewport_attachment)) { + // Let OSG manage the viewport automatically + camera->setViewport(new osg::Viewport); + SG_LOG(SG_INPUT, SG_WARN, "PassBuilder::build: Can't use attachment " + << viewport_attachment << " to resize the viewport"); + } + } + + for (auto const &p_attachment : p_attachments) { + try { + std::string buffer_name = p_attachment->getStringValue("buffer"); + if (buffer_name.empty()) + throw sg_exception("No buffer specified"); + + Buffer *buffer = compositor->getBuffer(buffer_name); + if (!buffer) + throw sg_exception(std::string("Unknown buffer '") + + buffer_name + "'"); + + osg::Texture *texture = buffer->texture; + + osg::Camera::BufferComponent component = osg::Camera::COLOR_BUFFER; + findPropString(p_attachment, "component", component, buffer_component_map); + + unsigned int level = p_attachment->getIntValue("level", 0); + unsigned int face = p_attachment->getIntValue("face", 0); + bool mipmap_generation = + p_attachment->getBoolValue("mipmap-generation", false); + unsigned int multisample_samples = + p_attachment->getIntValue("multisample-samples", 0); + unsigned int multisample_color_samples = + p_attachment->getIntValue("multisample-color-samples", 0); + + camera->attach(component, + texture, + level, + face, + mipmap_generation, + multisample_samples, + multisample_color_samples); + + if (!viewport_absolute && + (p_attachment->getIndex() == viewport_attachment)) { + if ((buffer->width_scale == 0.0f) && + (buffer->height_scale == 0.0f)) { + // This is a fixed size pass. We allow the user to use + // relative coordinates to shape the viewport. + float x = p_viewport->getFloatValue("x", 0.0f); + float y = p_viewport->getFloatValue("y", 0.0f); + float width = p_viewport->getFloatValue("width", 1.0f); + float height = p_viewport->getFloatValue("height", 1.0f); + camera->setViewport(x * texture->getTextureWidth(), + y * texture->getTextureHeight(), + width * texture->getTextureWidth(), + height * texture->getTextureHeight()); + } else { + // This is a pass that should match the physical viewport + // size. Store the scales so we can resize the pass later + // if the physical viewport changes size. + pass->viewport_width_scale = buffer->width_scale; + pass->viewport_height_scale = buffer->height_scale; + camera->setViewport( + 0, + 0, + buffer->width_scale * compositor->_viewport->width(), + buffer->height_scale * compositor->_viewport->height()); + } + } + } catch (sg_exception &e) { + SG_LOG(SG_INPUT, SG_ALERT, "PassBuilder::build: Skipping attachment " + << p_attachment->getIndex() << " in pass " << render_order + << ": " << e.what()); + } + } + } + + return pass.release(); +} + +//------------------------------------------------------------------------------ + +struct QuadPassBuilder : public PassBuilder { +public: + virtual Pass *build(Compositor *compositor, const SGPropertyNode *root) { + osg::ref_ptr pass = PassBuilder::build(compositor, root); + pass->useMastersSceneData = false; + + osg::Camera *camera = pass->camera; + camera->setAllowEventFocus(false); + camera->setViewMatrix(osg::Matrix::identity()); + camera->setProjectionMatrix(osg::Matrix::ortho2D(0, 1, 0, 1)); + + float left = 0.0f, bottom = 0.0f, width = 1.0f, height = 1.0f, scale = 1.0f; + const SGPropertyNode *p_geometry = root->getNode("geometry"); + if (p_geometry) { + left = p_geometry->getFloatValue("left", left); + bottom = p_geometry->getFloatValue("bottom", bottom); + width = p_geometry->getFloatValue("width", width); + height = p_geometry->getFloatValue("height", height); + scale = p_geometry->getFloatValue("scale", scale); + } + + const std::string eff_file = root->getStringValue("effect"); + + osg::ref_ptr quad = createFullscreenQuad( + left, bottom, width, height, scale, eff_file); + camera->addChild(quad); + + osg::StateSet *ss = camera->getOrCreateStateSet(); + for (const auto &uniform : compositor->getUniforms()) + ss->addUniform(uniform); + + return pass.release(); + } +protected: + osg::Geode *createFullscreenQuad(float left, + float bottom, + float width, + float height, + float scale, + const std::string &eff_file) { + osg::Geometry *geom; + + // When the quad is fullscreen, it can be optimized by using a + // a fullscreen triangle instead of a quad to avoid discarding pixels + // in the diagonal. If the desired geometry does not occupy the entire + // viewport, this optimization does not occur and a normal quad is drawn + // instead. + if (left != 0.0f || bottom != 0.0f || width != 1.0f || height != 1.0f + || scale != 1.0f) { + geom = osg::createTexturedQuadGeometry( + osg::Vec3(left, bottom, 0.0f), + osg::Vec3(width, 0.0f, 0.0f), + osg::Vec3(0.0f, height, 0.0f), + 0.0f, 0.0f, scale, scale); + } else { + geom = new osg::Geometry; + + osg::Vec3Array *coords = new osg::Vec3Array(3); + (*coords)[0].set(0.0f, 2.0f, 0.0f); + (*coords)[1].set(0.0f, 0.0f, 0.0f); + (*coords)[2].set(2.0f, 0.0f, 0.0f); + geom->setVertexArray(coords); + + osg::Vec2Array *tcoords = new osg::Vec2Array(3); + (*tcoords)[0].set(0.0f, 2.0f); + (*tcoords)[1].set(0.0f, 0.0f); + (*tcoords)[2].set(2.0f, 0.0f); + geom->setTexCoordArray(0, tcoords); + + osg::Vec4Array *colours = new osg::Vec4Array(1); + (*colours)[0].set(1.0f, 1.0f, 1.0, 1.0f); + geom->setColorArray(colours, osg::Array::BIND_OVERALL); + + osg::Vec3Array *normals = new osg::Vec3Array(1); + (*normals)[0].set(0.0f, 0.0f, 1.0f); + geom->setNormalArray(normals, osg::Array::BIND_OVERALL); + + geom->addPrimitiveSet(new osg::DrawArrays( + osg::PrimitiveSet::TRIANGLES, 0, 3)); + } + + osg::ref_ptr quad = new EffectGeode; + if (!eff_file.empty()) { + Effect *eff = makeEffect(eff_file, true, 0); + if (eff) + quad->setEffect(eff); + } + quad->addDrawable(geom); + quad->setCullingActive(false); + + osg::ref_ptr quad_state = quad->getOrCreateStateSet(); + int values = osg::StateAttribute::OFF | osg::StateAttribute::PROTECTED; + quad_state->setAttribute(new osg::PolygonMode( + osg::PolygonMode::FRONT_AND_BACK, + osg::PolygonMode::FILL), + values); + quad_state->setMode(GL_LIGHTING, values); + quad_state->setMode(GL_DEPTH_TEST, values); + + return quad.release(); + } +}; +RegisterPassBuilder registerQuadPass("quad"); + +//------------------------------------------------------------------------------ + +#if 0 +class ShadowMapUpdateCallback; // Forward declaration + +class ShadowMapCullCallback : public osg::NodeCallback { +public: + ShadowMapCullCallback(int light_num, float near_m, float far_m) : + _light_num(light_num), + _near_m(near_m), + _far_m(far_m) {} + + virtual void operator()(osg::Node *node, osg::NodeVisitor *nv) { + osg::Camera *light_camera = static_cast(node); + osgUtil::CullVisitor *cv = dynamic_cast(nv); + + osg::Vec4 light_pos = _light->getPosition(); + if (light_pos.w() != 0.0) { + // We only support directional light sources for now + traverse(node, nv); + return; + } + + osg::Vec3 light_dir = + osg::Vec3(light_pos.x(), light_pos.y(), light_pos.z()); + + // Find the light + // Mostly taken from osgShadow + osgUtil::RenderStage *rs = cv->getRenderStage(); + osgUtil::PositionalStateContainer::AttrMatrixList &aml = + rs->getPositionalStateContainer()->getAttrMatrixList(); + for (auto &itr : aml) { + const osg::Light *light = dynamic_cast(itr.first.get()); + if (light) { + if (light->getLightNum() != _light_num) + continue; + + osg::Vec4 light_pos = light->getPosition(); + if (light_pos.w() == 0.0) { + // We only want directional lights + light_dir = osg::Vec3(light_pos.x(), light_pos.y(), light_pos.z()); + } + } + } + traverse(node, nv); + } +protected: + friend ShadowMapUpdateCallback; + int _light_num; + float _near_m; + float _far_m; + osg::Matrix _camera_view; + osg::Matrix _camera_proj; + osg::observer_ptr _light; + osg::ref_ptr _light_matrix_uniform; +}; +#endif + +class LightFinder : public osg::NodeVisitor { +public: + LightFinder(const std::string &name) : + osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN), + _name(name) {} + virtual void apply(osg::Node &node) { + // Only traverse the scene graph if we haven't found a light yet (or if + // the one we found earlier is no longer valid). + if (getLight().valid()) + return; + + if (node.getName() == _name) { + osg::LightSource *light_source = + dynamic_cast(&node); + if (light_source) + _light = light_source->getLight(); + } + + traverse(node); + } + osg::ref_ptr getLight() const { + osg::ref_ptr light_ref; + _light.lock(light_ref); + return light_ref; + } +protected: + std::string _name; + osg::observer_ptr _light; +}; + +struct ShadowMapUpdateCallback : public Pass::PassUpdateCallback { +public: + ShadowMapUpdateCallback(const std::string &light_name, + float near_m, float far_m, + const std::string &suffix, + int sm_width, int sm_height) : + _light_finder(new LightFinder(light_name)), + _near_m(near_m), + _far_m(far_m) { + _light_matrix_uniform = new osg::Uniform( + osg::Uniform::FLOAT_MAT4, std::string("fg_LightMatrix_") + suffix); + _half_sm_size = osg::Vec2d((double)sm_width, (double)sm_height) / 2.0; + } + virtual void updatePass(Pass &pass, + const osg::Matrix &view_matrix, + const osg::Matrix &proj_matrix) { + osg::Camera *camera = pass.camera; + // Look for the light + camera->accept(*_light_finder); + osg::ref_ptr light = _light_finder->getLight(); + if (!light) { + // We could not find any light + return; + } + osg::Vec4 light_pos = light->getPosition(); + if (light_pos.w() != 0.0) { + // We only support directional light sources for now + return; + } + osg::Vec3 light_dir = + osg::Vec3(light_pos.x(), light_pos.y(), light_pos.z()); + + // The light direction we've just queried is from the previous frame. + // This is because the position of the osg::LightSource gets updated + // during the update traversal, and this function happens before that + // in the SubsystemMgr update. + // This is not a problem though (for now). + + osg::Matrix view_inverse = osg::Matrix::inverse(view_matrix); + + // Calculate the light's point of view transformation matrices. + // Taken from Project Rembrandt. + double left, right, bottom, top, zNear, zFar; + proj_matrix.getFrustum(left, right, bottom, top, zNear, zFar); + + osg::BoundingSphere bs; + bs.expandBy(osg::Vec3(left, bottom, -zNear) * (_near_m / zNear)); + bs.expandBy(osg::Vec3(right, top, -zNear) * (_far_m / zNear)); + bs.expandBy(osg::Vec3(left, bottom, -zNear) * (_far_m / zNear)); + bs.expandBy(osg::Vec3(right, top, -zNear) * (_near_m / zNear)); + + osg::Vec4 aim4 = osg::Vec4(bs.center(), 1.0) * view_inverse; + osg::Vec3 aim(aim4.x(), aim4.y(), aim4.z()); + osg::Vec3 up(0.0f, 1.0f, 0.0f); + + osg::Matrixd &light_view_matrix = camera->getViewMatrix(); + light_view_matrix.makeLookAt( + aim + (light_dir * bs.radius() * 2.0f), + aim, + aim); + + osg::Matrixd &light_proj_matrix = camera->getProjectionMatrix(); + light_proj_matrix.makeOrtho( + -bs.radius(), bs.radius(), + -bs.radius(), bs.radius(), + -bs.radius() * 6.0f, bs.radius() * 6.0f); + + // Do texel snapping to prevent flickering or shimmering. + // We are using double precision vectors and matrices because in FG + // world coordinates are relative to the center of the Earth, which can + // (and will) cause precision issues due to their magnitude. + osg::Vec4d shadow_origin4 = osg::Vec4d(0.0, 0.0, 0.0, 1.0) * + light_view_matrix * light_proj_matrix; + osg::Vec2d shadow_origin(shadow_origin4.x(), shadow_origin4.y()); + shadow_origin = osg::Vec2d(shadow_origin.x() * _half_sm_size.x(), + shadow_origin.y() * _half_sm_size.y()); + osg::Vec2d rounded_origin(std::round(shadow_origin.x()), + std::round(shadow_origin.y())); + osg::Vec2d rounding = rounded_origin - shadow_origin; + rounding = osg::Vec2d(rounding.x() / _half_sm_size.x(), + rounding.y() / _half_sm_size.y()); + + osg::Matrixd round_matrix = osg::Matrixd::translate( + rounding.x(), rounding.y(), 0.0); + light_proj_matrix *= round_matrix; + + osg::Matrixf light_matrix = + // Include the real camera inverse view matrix because if the shader + // used world coordinates, there would be precision issues. + view_inverse * + camera->getViewMatrix() * + camera->getProjectionMatrix() * + // Bias matrices + osg::Matrix::translate(1.0, 1.0, 1.0) * + osg::Matrix::scale(0.5, 0.5, 0.5); + _light_matrix_uniform->set(light_matrix); + } + + osg::Uniform *getLightMatrixUniform() const { + return _light_matrix_uniform.get(); + } +protected: + osg::ref_ptr _light_finder; + float _near_m; + float _far_m; + osg::ref_ptr _light_matrix_uniform; + osg::Vec2d _half_sm_size; +}; + +struct ShadowMapPassBuilder : public PassBuilder { + virtual Pass *build(Compositor *compositor, const SGPropertyNode *root) { + osg::ref_ptr pass = PassBuilder::build(compositor, root); + + osg::Camera *camera = pass->camera; + camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF_INHERIT_VIEWPOINT); + + std::string light_name = root->getStringValue("light-name"); + float near_m = root->getFloatValue("near-m"); + float far_m = root->getFloatValue("far-m"); + int sm_width = camera->getViewport()->width(); + int sm_height = camera->getViewport()->height(); + pass->update_callback = new ShadowMapUpdateCallback( + light_name, + near_m, far_m, + pass->name, + sm_width, sm_height); + + return pass.release(); + } +}; +RegisterPassBuilder registerShadowMapPass("shadow-map"); + +//------------------------------------------------------------------------------ + +class SceneUpdateCallback : public Pass::PassUpdateCallback { +public: + SceneUpdateCallback(int cubemap_face, float zNear, float zFar) : + _cubemap_face(cubemap_face), + _zNear(zNear), + _zFar(zFar) {} + + virtual void updatePass(Pass &pass, + const osg::Matrix &view_matrix, + const osg::Matrix &proj_matrix) { + osg::Camera *camera = pass.camera; + if (_cubemap_face < 0) { + camera->setViewMatrix(view_matrix); + camera->setProjectionMatrix(proj_matrix); + } else { + osg::Vec3 camera_pos = osg::Vec3(0.0, 0.0, 0.0) * + osg::Matrix::inverse(view_matrix); + + typedef std::pair CubemapFace; + const CubemapFace id[] = { + CubemapFace(osg::Vec3( 1, 0, 0), osg::Vec3( 0, -1, 0)), // +X + CubemapFace(osg::Vec3(-1, 0, 0), osg::Vec3( 0, -1, 0)), // -X + CubemapFace(osg::Vec3( 0, 1, 0), osg::Vec3( 0, 0, 1)), // +Y + CubemapFace(osg::Vec3( 0, -1, 0), osg::Vec3( 0, 0, -1)), // -Y + CubemapFace(osg::Vec3( 0, 0, 1), osg::Vec3( 0, -1, 0)), // +Z + CubemapFace(osg::Vec3( 0, 0, -1), osg::Vec3( 0, -1, 0)) // -Z + }; + + osg::Matrix cubemap_view_matrix; + cubemap_view_matrix.makeLookAt(camera_pos, + camera_pos + id[_cubemap_face].first, + camera_pos + id[_cubemap_face].second); + camera->setViewMatrix(cubemap_view_matrix); + camera->setProjectionMatrixAsFrustum(-1.0, 1.0, -1.0, 1.0, + 1.0, 10000.0); + } + + if (_zNear != 0.0f && _zFar != 0.0f) { + osg::Matrix new_proj; + makeNewProjMat(camera->getProjectionMatrix(), + _zNear, _zFar, new_proj); + camera->setProjectionMatrix(new_proj); + } + } +protected: + // Given a projection matrix, return a new one with the same frustum + // sides and new near / far values. + void makeNewProjMat(Matrixd& oldProj, double znear, + double zfar, Matrixd& projection) { + projection = oldProj; + // Slightly inflate the near & far planes to avoid objects at the + // extremes being clipped out. + znear *= 0.999; + zfar *= 1.001; + + // Clamp the projection matrix z values to the range (near, far) + double epsilon = 1.0e-6; + if (fabs(projection(0,3)) < epsilon && + fabs(projection(1,3)) < epsilon && + fabs(projection(2,3)) < epsilon) { + // Projection is Orthographic + epsilon = -1.0/(zfar - znear); // Used as a temp variable + projection(2,2) = 2.0*epsilon; + projection(3,2) = (zfar + znear)*epsilon; + } else { + // Projection is Perspective + double trans_near = (-znear*projection(2,2) + projection(3,2)) / + (-znear*projection(2,3) + projection(3,3)); + double trans_far = (-zfar*projection(2,2) + projection(3,2)) / + (-zfar*projection(2,3) + projection(3,3)); + double ratio = fabs(2.0/(trans_near - trans_far)); + double center = -0.5*(trans_near + trans_far); + + projection.postMult(osg::Matrixd(1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, ratio, 0.0, + 0.0, 0.0, center*ratio, 1.0)); + } + } + + int _cubemap_face; + float _zNear; + float _zFar; +}; + +struct ScenePassBuilder : public PassBuilder { +public: + virtual Pass *build(Compositor *compositor, const SGPropertyNode *root) { + osg::ref_ptr pass = PassBuilder::build(compositor, root); + + osg::Camera *camera = pass->camera; + camera->setAllowEventFocus(true); + + const SGPropertyNode *clustered = root->getChild("clustered-forward"); + if (clustered) { + camera->setInitialDrawCallback(new ClusteredForwardDrawCallback); + } + + int cubemap_face = root->getIntValue("cubemap-face", -1); + float zNear = root->getFloatValue("z-near", 0.0f); + float zFar = root->getFloatValue("z-far", 0.0f); + pass->update_callback = new SceneUpdateCallback(cubemap_face, zNear, zFar); + + std::string shadow_pass_name = root->getStringValue("use-shadow-pass"); + if (!shadow_pass_name.empty()) { + Pass *shadow_pass = compositor->getPass(shadow_pass_name); + if (shadow_pass) { + ShadowMapUpdateCallback *updatecb = + dynamic_cast( + shadow_pass->update_callback.get()); + if (updatecb) { + camera->getOrCreateStateSet()->addUniform( + updatecb->getLightMatrixUniform()); + } else { + SG_LOG(SG_INPUT, SG_WARN, "ScenePassBuilder::build: Pass '" + << shadow_pass_name << "is not a shadow pass"); + } + } else { + SG_LOG(SG_INPUT, SG_WARN, "ScenePassBuilder::build: Could not " + "find shadow pass named '" << shadow_pass_name << "'"); + } + } + + return pass.release(); + } +}; + +RegisterPassBuilder registerScenePass("scene"); + +//------------------------------------------------------------------------------ + +Pass * +buildPass(Compositor *compositor, const SGPropertyNode *root) +{ + std::string type = root->getStringValue("type"); + if (type.empty()) { + SG_LOG(SG_INPUT, SG_ALERT, "buildPass: Unspecified pass type"); + return 0; + } + PassBuilder *builder = PassBuilder::find(type); + if (!builder) { + SG_LOG(SG_INPUT, SG_ALERT, "buildPass: Unknown pass type '" + << type << "'"); + return 0; + } + + return builder->build(compositor, root); +} + +} // namespace compositor +} // namespace simgear diff --git a/simgear/scene/viewer/CompositorPass.hxx b/simgear/scene/viewer/CompositorPass.hxx new file mode 100644 index 00000000..cb59a83c --- /dev/null +++ b/simgear/scene/viewer/CompositorPass.hxx @@ -0,0 +1,134 @@ +// Copyright (C) 2018 Fernando García Liñán +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Library General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Library General Public License for more details. +// +// You should have received a copy of the GNU Library General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + +#ifndef SG_COMPOSITOR_PASS_HXX +#define SG_COMPOSITOR_PASS_HXX + +#include + +#include +#include + +#include +#include +#include +#include + +namespace simgear { +namespace compositor { + +class Compositor; + +/** + * A Pass encapsulates a single render operation. In an OSG context, this is + * best represented as a Camera attached to the Viewer as a slave camera. + * + * Passes can render directly to the framebuffer or to a texture via FBOs. Also, + * the OpenGL state can be modified via the Effects framework and by exposing RTT + * textures from previous passes. + * + * Every pass can be enabled and disabled via a property tree conditional + * expression. This allows dynamic rendering pipelines where features can be + * enabled or disabled in a coherent way by the user. + */ +struct Pass : public osg::Referenced { + Pass() : + useMastersSceneData(true), + cull_mask(0xffffff), + viewport_width_scale(0.0f), + viewport_height_scale(0.0f) {} + + bool isEnabled() const { return (condition == 0) || condition->test(); } + + std::string name; + std::string type; + osg::ref_ptr camera; + SGSharedPtr condition; + /** If null, there is no effect override for this pass. */ + osg::ref_ptr effect_override; + bool useMastersSceneData; + osg::Node::NodeMask cull_mask; + float viewport_width_scale; + float viewport_height_scale; + + struct PassUpdateCallback : public virtual osg::Referenced { + public: + virtual void updatePass(Pass &pass, + const osg::Matrix &view_matrix, + const osg::Matrix &proj_matrix) = 0; + }; + + osg::ref_ptr update_callback; +}; + +class PassBuilder : public SGReferenced { +public: + virtual ~PassBuilder() {} + + /** + * \brief Build a pass. + * + * By default, this function implements commonly used features such as + * input/output buffers, conditional support etc., but can be safely ignored + * and overrided for more special passes. + * + * @param compositor The Compositor instance that owns the pass. + * @param The root node of the pass property tree. + * @return A Pass or a null pointer if an error occurred. + */ + virtual Pass *build(Compositor *compositor, const SGPropertyNode *root); + + static PassBuilder *find(const std::string &type) { + auto itr = PassBuilderMapSingleton::instance()->_map.find(type); + if (itr == PassBuilderMapSingleton::instance()->_map.end()) + return 0; + return itr->second.ptr(); + } +protected: + typedef std::unordered_map> PassBuilderMap; + struct PassBuilderMapSingleton : public Singleton { + PassBuilderMap _map; + }; + template + friend struct RegisterPassBuilder; +}; + +/** + * An instance of this type registers a new pass type T with a name. + * A global instance of this class must be created in CompositorPass.cxx to + * register a new pass type. + */ +template +struct RegisterPassBuilder { + RegisterPassBuilder(const std::string &name) { + PassBuilder::PassBuilderMapSingleton::instance()-> + _map.insert(std::make_pair(name, new T)); + } +}; + +/** + * \brief Create a pass from a property tree definition. + * + * @param comp The Compositor instance that owns the pass. + * @param node The root node of the pass property tree. + * @return A Pass or a null pointer if an error occurred. + */ +Pass *buildPass(Compositor *compositor, const SGPropertyNode *root); + +} // namespace compositor +} // namespace simgear + +#endif /* SG_COMPOSITOR_PASS_HXX */ From 7246edcb4b5180bf14c25a012562120ff0805fac Mon Sep 17 00:00:00 2001 From: Scott Giese Date: Tue, 5 Feb 2019 12:20:06 -0600 Subject: [PATCH 03/33] [boost::enable_if] Support Boost versions < 1.56 --- simgear/nasal/cppbind/NasalHash.hxx | 4 ++++ simgear/props/props.hxx | 6 +++++- simgear/structure/SGWeakReferenced.hxx | 4 ++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/simgear/nasal/cppbind/NasalHash.hxx b/simgear/nasal/cppbind/NasalHash.hxx index 5019d08b..59281643 100644 --- a/simgear/nasal/cppbind/NasalHash.hxx +++ b/simgear/nasal/cppbind/NasalHash.hxx @@ -24,7 +24,11 @@ #include #include +#if BOOST_VERSION >= 105600 #include +#else +#include +#endif namespace nasal { diff --git a/simgear/props/props.hxx b/simgear/props/props.hxx index 68d3c03c..3afc8796 100644 --- a/simgear/props/props.hxx +++ b/simgear/props/props.hxx @@ -56,7 +56,11 @@ namespace boost { } #else # include -# include +#if BOOST_VERSION >= 105600 +#include +#else +#include +#endif # include # include diff --git a/simgear/structure/SGWeakReferenced.hxx b/simgear/structure/SGWeakReferenced.hxx index 27f647b4..24db41b8 100644 --- a/simgear/structure/SGWeakReferenced.hxx +++ b/simgear/structure/SGWeakReferenced.hxx @@ -22,7 +22,11 @@ #include "SGSharedPtr.hxx" #include +#if BOOST_VERSION >= 105600 #include +#else +#include +#endif #ifdef _MSC_VER # pragma warning(push) From 2303da846b0f59840b5eb5ca02153b0fad346783 Mon Sep 17 00:00:00 2001 From: Scott Giese Date: Tue, 5 Feb 2019 21:46:02 -0600 Subject: [PATCH 04/33] Fix missing header for OSG macros. Patch provided by Stewart Andreason. --- simgear/scene/material/TextureBuilder.cxx | 1 + simgear/scene/model/BVHPageNodeOSG.cxx | 1 + simgear/scene/model/SGReaderWriterXML.cxx | 1 + simgear/scene/model/modellib.cxx | 1 + simgear/scene/tgdb/ReaderWriterSPT.cxx | 1 + 5 files changed, 5 insertions(+) diff --git a/simgear/scene/material/TextureBuilder.cxx b/simgear/scene/material/TextureBuilder.cxx index 43343169..38478d82 100644 --- a/simgear/scene/material/TextureBuilder.cxx +++ b/simgear/scene/material/TextureBuilder.cxx @@ -23,6 +23,7 @@ #include "Pass.hxx" +#include #include #include #include diff --git a/simgear/scene/model/BVHPageNodeOSG.cxx b/simgear/scene/model/BVHPageNodeOSG.cxx index a2f15bcb..326585ab 100644 --- a/simgear/scene/model/BVHPageNodeOSG.cxx +++ b/simgear/scene/model/BVHPageNodeOSG.cxx @@ -24,6 +24,7 @@ #include "../../bvh/BVHPageRequest.hxx" #include "../../bvh/BVHPager.hxx" +#include #include #include #include diff --git a/simgear/scene/model/SGReaderWriterXML.cxx b/simgear/scene/model/SGReaderWriterXML.cxx index 64ad2754..71f63310 100644 --- a/simgear/scene/model/SGReaderWriterXML.cxx +++ b/simgear/scene/model/SGReaderWriterXML.cxx @@ -27,6 +27,7 @@ #include +#include #include #include #include diff --git a/simgear/scene/model/modellib.cxx b/simgear/scene/model/modellib.cxx index d11e0d21..92b19196 100644 --- a/simgear/scene/model/modellib.cxx +++ b/simgear/scene/model/modellib.cxx @@ -21,6 +21,7 @@ #include +#include #include #include #include diff --git a/simgear/scene/tgdb/ReaderWriterSPT.cxx b/simgear/scene/tgdb/ReaderWriterSPT.cxx index 66fad24b..638ce476 100644 --- a/simgear/scene/tgdb/ReaderWriterSPT.cxx +++ b/simgear/scene/tgdb/ReaderWriterSPT.cxx @@ -25,6 +25,7 @@ #include +#include #include #include #include From 95d065b3d78af866edec7f484d2373be79d7e3bc Mon Sep 17 00:00:00 2001 From: James Turner Date: Wed, 6 Feb 2019 12:34:51 +0000 Subject: [PATCH 05/33] Add strutils::iequals helper Replaces another boost method --- simgear/misc/strutils.cxx | 16 ++++++++++++++++ simgear/misc/strutils.hxx | 5 +++++ simgear/misc/strutils_test.cxx | 11 +++++++++++ 3 files changed, 32 insertions(+) diff --git a/simgear/misc/strutils.cxx b/simgear/misc/strutils.cxx index 88bf2d21..22f0d810 100644 --- a/simgear/misc/strutils.cxx +++ b/simgear/misc/strutils.cxx @@ -623,6 +623,22 @@ namespace simgear { *p = tolower(*p); } } + + +bool iequals(const std::string& a, const std::string& b) +{ + const auto lenA = a.length(); + const auto lenB = b.length(); + if (lenA != lenB) return false; + + const char* aPtr = a.data(); + const char* bPtr = b.data(); + for (size_t i = 0; i < lenA; ++i) { + if (tolower(*aPtr++) != tolower(*bPtr++)) return false; + } + + return true; +} #if defined(SG_WINDOWS) static std::wstring convertMultiByteToWString(DWORD encoding, const std::string& a) diff --git a/simgear/misc/strutils.hxx b/simgear/misc/strutils.hxx index 74fb7f39..d8fe9fbb 100644 --- a/simgear/misc/strutils.hxx +++ b/simgear/misc/strutils.hxx @@ -264,6 +264,11 @@ namespace simgear { */ void lowercase(std::string &s); + /** + * case-insensitive string comparisom + */ + bool iequals(const std::string& a, const std::string& b); + /** * convert a string in the local Windows 8-bit encoding to UTF-8 * (no-op on other platforms) diff --git a/simgear/misc/strutils_test.cxx b/simgear/misc/strutils_test.cxx index d9559ad3..b78b9a63 100644 --- a/simgear/misc/strutils_test.cxx +++ b/simgear/misc/strutils_test.cxx @@ -99,6 +99,16 @@ void test_to_int() SG_CHECK_EQUAL(strutils::to_int("-10000"), -10000); } +void test_iequals() +{ + SG_VERIFY(strutils::iequals("abcdef", "AbCDeF")); + SG_VERIFY(strutils::iequals("", "")); + SG_VERIFY(!strutils::iequals("abcdE", "ABCD")); + SG_VERIFY(strutils::iequals("%$abcdef12", "%$AbCDeF12")); + SG_VERIFY(strutils::iequals("VOR-DME", "vor-dme")); + SG_VERIFY(!strutils::iequals("VOR-DME", "vor_dme")); +} + // Auxiliary function for test_readNonNegativeInt() void aux_readNonNegativeInt_setUpOStringStream(std::ostringstream& oss, int base) { @@ -737,6 +747,7 @@ int main(int argc, char* argv[]) test_utf8Convert(); test_parseGeod(); test_formatGeod(); + test_iequals(); return EXIT_SUCCESS; } From c1dd8faa29136b30272e26042d24f079d5ec2b7c Mon Sep 17 00:00:00 2001 From: Richard Harrison Date: Sun, 10 Feb 2019 17:44:45 +0100 Subject: [PATCH 06/33] Improved subsystem performance monitoring and overrun detection This is to help diagnose where performance is being used and augments the current performance monitor. The easiest way to active this is to open the performance monitor; however setting the enable property will enable less intrusive logging and overrun detection. Using the GUI performance monitor itself affects the performance when it updates. Properties /sim/performance-monitor/enabled - need to be true to be active (unchanged) /sim/performance-monitor/dump-stats - hierarchical dump to console of all current timing stats for all subsysems /sim/performance-monitor/interval-s - reporting interval (unchanged) /sim/performance-monitor/max-time-per-frame-ms - any subsystem that takes more than this amount of will be logged to the console --- simgear/structure/SGPerfMon.cxx | 12 +- simgear/structure/SGPerfMon.hxx | 2 + simgear/structure/event_mgr.cxx | 20 +-- simgear/structure/event_mgr.hxx | 4 +- simgear/structure/subsystem_mgr.cxx | 182 +++++++++++++++++++++++++--- simgear/structure/subsystem_mgr.hxx | 35 +++++- 6 files changed, 221 insertions(+), 34 deletions(-) diff --git a/simgear/structure/SGPerfMon.cxx b/simgear/structure/SGPerfMon.cxx index ae8191b5..c4d7caed 100644 --- a/simgear/structure/SGPerfMon.cxx +++ b/simgear/structure/SGPerfMon.cxx @@ -51,7 +51,10 @@ SGPerformanceMonitor::bind(void) { _statiticsSubsystems = _root->getChild("subsystems", 0, true); _statisticsFlag = _root->getChild("enabled", 0, true); + _timingDetailsFlag = _root->getChild("dump-stats", 0, true); + _timingDetailsFlag->setBoolValue(false); _statisticsInterval = _root->getChild("interval-s", 0, true); + _maxTimePerFrame_ms = _root->getChild("max-time-per-frame-ms", 0, true); } void @@ -60,6 +63,7 @@ SGPerformanceMonitor::unbind(void) _statiticsSubsystems = 0; _statisticsFlag = 0; _statisticsInterval = 0; + _maxTimePerFrame_ms = 0; } void @@ -83,7 +87,10 @@ SGPerformanceMonitor::update(double dt) else _subSysMgr->setReportTimingCb(this,0); } - + if (_timingDetailsFlag->getBoolValue()) { + _subSysMgr->setReportTimingStats(true); + _timingDetailsFlag->setBoolValue(false); + } if (!_isEnabled) return; @@ -94,6 +101,9 @@ SGPerformanceMonitor::update(double dt) _subSysMgr->reportTiming(); _lastUpdate.stamp(); } + if (_maxTimePerFrame_ms) { + SGSubsystem::maxTimePerFrame_ms = _maxTimePerFrame_ms->getIntValue(); + } } /** Callback hooked into the subsystem manager. */ diff --git a/simgear/structure/SGPerfMon.hxx b/simgear/structure/SGPerfMon.hxx index d7e4ed59..32d642cc 100644 --- a/simgear/structure/SGPerfMon.hxx +++ b/simgear/structure/SGPerfMon.hxx @@ -52,8 +52,10 @@ private: SGSubsystemMgr* _subSysMgr; SGPropertyNode_ptr _root; SGPropertyNode_ptr _statiticsSubsystems; + SGPropertyNode_ptr _timingDetailsFlag; SGPropertyNode_ptr _statisticsFlag; SGPropertyNode_ptr _statisticsInterval; + SGPropertyNode_ptr _maxTimePerFrame_ms; bool _isEnabled; int _count; diff --git a/simgear/structure/event_mgr.cxx b/simgear/structure/event_mgr.cxx index 435b18f8..dead48b7 100644 --- a/simgear/structure/event_mgr.cxx +++ b/simgear/structure/event_mgr.cxx @@ -47,7 +47,7 @@ SGEventMgr::SGEventMgr() : _inited(false), _shutdown(false) { - +_name = "EventMgr"; } SGEventMgr::~SGEventMgr() @@ -86,10 +86,10 @@ void SGEventMgr::shutdown() void SGEventMgr::update(double delta_time_sec) { - _simQueue.update(delta_time_sec); - + _simQueue.update(delta_time_sec, _timerStats); + double rt = _rtProp ? _rtProp->getDoubleValue() : 0; - _rtQueue.update(rt); + _rtQueue.update(rt, _timerStats); } void SGEventMgr::removeTask(const std::string& name) @@ -169,19 +169,23 @@ void SGTimerQueue::clear() _table[i].timer = 0; } } - -void SGTimerQueue::update(double deltaSecs) +int maxTimerQueuePerItem_us = 30; +void SGTimerQueue::update(double deltaSecs, std::map &timingStats) { _now += deltaSecs; - while(_numEntries && nextTime() <= _now) { + + while (_numEntries && nextTime() <= _now) { SGTimer* t = remove(); - if(t->repeat) + if (t->repeat) insert(t, t->interval); // warning: this is not thread safe // but the entire timer queue isn't either + SGTimeStamp timeStamp; + timeStamp.stamp(); t->running = true; t->run(); t->running = false; + timingStats[t->name] += timeStamp.elapsedMSec() / 1000.0; if (!t->repeat) delete t; } diff --git a/simgear/structure/event_mgr.hxx b/simgear/structure/event_mgr.hxx index eaaec7f0..70d516fb 100644 --- a/simgear/structure/event_mgr.hxx +++ b/simgear/structure/event_mgr.hxx @@ -24,9 +24,8 @@ class SGTimerQueue { public: SGTimerQueue(int preSize=1); ~SGTimerQueue(); - void clear(); - void update(double deltaSecs); + void update(double deltaSecs, std::map &timingStats); double now() { return _now; } @@ -78,7 +77,6 @@ public: virtual void update(double delta_time_sec); virtual void unbind(); virtual void shutdown(); - void setRealtimeProperty(SGPropertyNode* node) { _rtProp = node; } /** diff --git a/simgear/structure/subsystem_mgr.cxx b/simgear/structure/subsystem_mgr.cxx index fc29e883..799457f4 100644 --- a/simgear/structure/subsystem_mgr.cxx +++ b/simgear/structure/subsystem_mgr.cxx @@ -45,8 +45,10 @@ using State = SGSubsystem::State; SGSubsystemTimingCb SGSubsystem::reportTimingCb = NULL; void* SGSubsystem::reportTimingUserData = NULL; +bool SGSubsystem::reportTimingStatsRequest = false; +int SGSubsystem::maxTimePerFrame_ms = 7; -SGSubsystem::SGSubsystem () +SGSubsystem::SGSubsystem () : _executionTime(0), _lastExecutionTime(0) { } @@ -202,16 +204,20 @@ std::string SGSubsystem::nameForState(State s) class SGSubsystemGroup::Member { private: - Member (const Member &member); + Member(const Member &member); public: - Member (); + Member(); ~Member (); void update (double delta_time_sec); void reportTiming(void) { if (reportTimingCb) reportTimingCb(reportTimingUserData, name, &timeStat); } - void updateExecutionTime(double time) { timeStat += time;} + void reportTimingStats(TimerStats *_lastValues) { + if (subsystem) + subsystem->reportTimingStats(_lastValues); + } + void updateExecutionTime(double time) { timeStat += time;} SampleStatistic timeStat; std::string name; SGSubsystemRef subsystem; @@ -220,15 +226,18 @@ public: bool collectTimeStats; int exceptionCount; int initTime; + + void mergeTimerStats(SGSubsystem::TimerStats &stats); }; -SGSubsystemGroup::SGSubsystemGroup () : - _fixedUpdateTime(-1.0), - _updateTimeRemainder(0.0), - _initPosition(-1) +SGSubsystemGroup::SGSubsystemGroup(const std::string &name) : + _fixedUpdateTime(-1.0), + _updateTimeRemainder(0.0), + _initPosition(-1) { + _name = name; } SGSubsystemGroup::~SGSubsystemGroup () @@ -377,21 +386,145 @@ SGSubsystemGroup::update (double delta_time_sec) const bool recordTime = (reportTimingCb != nullptr); SGTimeStamp timeStamp; + TimerStats lvTimerStats(_timerStats); + TimerStats overrunItems; + bool overrun = false; + + SGTimeStamp outerTimeStamp; + outerTimeStamp.stamp(); while (loopCount-- > 0) { for (auto member : _members) { - if (recordTime) - timeStamp = SGTimeStamp::now(); + timeStamp.stamp(); + if (member->subsystem->_timerStats.size()) { + member->subsystem->_lastTimerStats.clear(); + member->subsystem->_lastTimerStats.insert(member->subsystem->_timerStats.begin(), member->subsystem->_timerStats.end()); + } member->update(delta_time_sec); // indirect call + if (member->name.size()) + _timerStats[member->name] += timeStamp.elapsedMSec() / 1000.0; if (recordTime && reportTimingCb) { - timeStamp = SGTimeStamp::now() - timeStamp; - member->updateExecutionTime(timeStamp.toUSecs()); + member->updateExecutionTime(timeStamp.elapsedMSec()*1000); + if (timeStamp.elapsedMSec() > SGSubsystemMgr::maxTimePerFrame_ms) { + overrunItems[member->name] += timeStamp.elapsedMSec(); + overrun = true; + } } } } // of multiple update loop + _lastExecutionTime = _executionTime; + _executionTime += outerTimeStamp.elapsedMSec(); + if (overrun) { + for (auto overrunItem : overrunItems) { + SG_LOG(SG_EVENT, SG_ALERT, "Subsystem " + << overrunItem.first + << " total " + << std::setw(6) << std::fixed << std::setprecision(2) << std::right << _timerStats[overrunItem.first] + << "s overrun " + << std::setw(6) << std::fixed << std::setprecision(2) << std::right << overrunItem.second + << "ms"); + auto m = std::find_if(_members.begin(), _members.end(), [overrunItem](const Member* m) { + if (m->name == overrunItem.first) + return true; + return false; + }); + if (m != _members.end()) { + auto member = *m; + if (overrunItems[member->name]) { + TimerStats sst; + member->reportTimingStats(&_lastTimerStats); + //if (lvTimerStats[member->name] != _timerStats[member->name]) { + // SG_LOG(SG_EVENT, SG_ALERT, + // " +" << std::setw(6) << std::left << (_timerStats[member->name] - lvTimerStats[member->name]) + // << " total " << std::setw(6) << std::left << _timerStats[member->name] + // << " " << member->name + // ); + //} + } + } + } + } + + if (reportTimingStatsRequest) { + reportTimingStats(nullptr); + //for (auto member : _members) { + // member->mergeTimerStats(_timerStats); + // if (_timerStats.size()) { + // SG_LOG(SG_EVENT, SG_ALERT, "" << std::setw(6) << std::fixed << std::setprecision(2) << std::left << _timerStats[member->name] + // << ": " << member->name); + // for (auto item : _timerStats) { + // if (item.second > 0) + // SG_LOG(SG_EVENT, SG_ALERT, " " << std::setw(6) << std::left << item.second << " " << item.first); + // } + // } + //} + } + _lastTimerStats.clear(); + _lastTimerStats.insert(_timerStats.begin(), _timerStats.end()); + +} +void SGSubsystem::reportTimingStats(TimerStats *__lastValues) { + std::string _name = ""; + + bool reportDeltas = __lastValues != nullptr; + __lastValues = &_lastTimerStats; + std::ostringstream t; + if (reportDeltas) { + auto deltaT = _executionTime - _lastExecutionTime; + if (deltaT != 0) { + t << name() << "(+" << std::setprecision(2) << std::right << deltaT << "ms)."; + _name = t.str(); + } + } + else { + SG_LOG(SG_EVENT, SG_ALERT, "SubSystem: " << _name << " " << std::setw(6) << std::setprecision(4) << std::right << _executionTime / 1000.0 << "s"); + } + for (auto item : _timerStats) { + std::ostringstream output; + if (item.second > 0) { + if (reportDeltas) + { + auto delta = item.second - (*__lastValues)[item.first]; + if (delta != 0) { + output + << " +" << std::setw(6) << std::setprecision(4) << std::left << (delta * 1000.0) + << _name << item.first + << " total " << std::setw(6) << std::setprecision(4) << std::right << item.second << "s " + ; + } + } + else + output << " " << std::setw(6) << std::setprecision(4) << std::right << item.second << "s " << item.first; + if (output.str().size()) + SG_LOG(SG_EVENT, SG_ALERT, output.str()); + } + } } +void SGSubsystemGroup::reportTimingStats(TimerStats *_lastValues) { + SGSubsystem::reportTimingStats(_lastValues); + + std::string _name = name(); + if (!_name.size()) + _name = typeid(this).name(); + if (_lastValues) { + auto deltaT = _executionTime - _lastExecutionTime; + if (deltaT != 0) { + SG_LOG(SG_EVENT, SG_ALERT, + " +" << std::setw(6) << std::setprecision(4) << std::right << deltaT << "ms " + << name() ); + } + } + else + SG_LOG(SG_EVENT, SG_ALERT, "SubSystemGroup: " << name() << " " << std::setw(6) << std::setprecision(4) << std::right << _executionTime / 1000.0 << "s"); + for (auto member : _members) { + member->reportTimingStats(_lastValues); + } + _lastTimerStats.clear(); + _lastTimerStats.insert(_timerStats.begin(), _timerStats.end()); + +} void SGSubsystemGroup::reportTiming(void) { @@ -609,6 +742,7 @@ auto SGSubsystemGroup::get_member(const string &name, bool create) -> Member* Member* m = new Member; m->name = name; + _timerStats[name] = 0; _members.push_back(m); return _members.back(); } @@ -653,6 +787,11 @@ SGSubsystemGroup::Member::Member (const Member &) SGSubsystemGroup::Member::~Member () { } +void SGSubsystemGroup::Member::mergeTimerStats(SGSubsystem::TimerStats &stats) { + stats.insert(subsystem->_timerStats.begin(), subsystem->_timerStats.end()); + //for (auto ts : subsystem->_timerStats) + // ts.second = 0; +} void SGSubsystemGroup::Member::update (double delta_time_sec) @@ -666,10 +805,15 @@ SGSubsystemGroup::Member::update (double delta_time_sec) return; } + SGTimeStamp oTimer; try { - subsystem->update(elapsed_sec); - elapsed_sec = 0; - } catch (sg_exception& e) { + oTimer.stamp(); + subsystem->update(elapsed_sec); + subsystem->_lastExecutionTime = subsystem->_executionTime; + subsystem->_executionTime += oTimer.elapsedMSec(); + elapsed_sec = 0; + } + catch (sg_exception& e) { SG_LOG(SG_GENERAL, SG_ALERT, "caught exception processing subsystem:" << name << "\nmessage:" << e.getMessage()); @@ -693,7 +837,7 @@ namespace { } // end of anonymous namespace -SGSubsystemMgr::SGSubsystemMgr () : +SGSubsystemMgr::SGSubsystemMgr (const std::string &name) : _groups(MAX_GROUPS) { if (global_defaultSubsystemManager == nullptr) { @@ -710,7 +854,7 @@ SGSubsystemMgr::SGSubsystemMgr () : #endif for (int i = 0; i < MAX_GROUPS; i++) { - auto g = new SGSubsystemGroup; + auto g = new SGSubsystemGroup(name); g->set_manager(this); _groups[i].reset(g); } @@ -746,6 +890,7 @@ SGSubsystemMgr::init () _groups[i]->init(); } + SGSubsystem::InitStatus SGSubsystemMgr::incrementalInit() { @@ -802,9 +947,12 @@ SGSubsystemMgr::unbind () void SGSubsystemMgr::update (double delta_time_sec) { + SGTimeStamp timeStamp; + for (int i = 0; i < MAX_GROUPS; i++) { _groups[i]->update(delta_time_sec); } + reportTimingStatsRequest = false; } void diff --git a/simgear/structure/subsystem_mgr.hxx b/simgear/structure/subsystem_mgr.hxx index 95cd5a46..3a5c4013 100644 --- a/simgear/structure/subsystem_mgr.hxx +++ b/simgear/structure/subsystem_mgr.hxx @@ -130,7 +130,8 @@ typedef void (*SGSubsystemTimingCb)(void* userData, const std::string& name, Sam class SGSubsystem : public SGReferenced { public: - /** + using TimerStats = std::map; + /** * Default constructor. */ SGSubsystem (); @@ -270,6 +271,7 @@ public: */ void reportTiming(void); + virtual void reportTimingStats(TimerStats *_lastValues); /** * Place time stamps at strategic points in the execution of subsystems * update() member functions. Predominantly for debugging purposes. @@ -323,6 +325,20 @@ public: * debug helper, print a state as a string */ static std::string nameForState(State s); + + /** + * gets fine grained stats of time elapsed since last clear + * returns map of ident and time + */ + virtual const TimerStats &getTimerStats() { + return _timerStats; + } + + /** + * clear fine grained stats that are over the specified value. + */ + virtual void resetTimerStats(double val = 0) { } + protected: friend class SGSubsystemMgr; friend class SGSubsystemGroup; @@ -342,9 +358,16 @@ protected: static SGSubsystemTimingCb reportTimingCb; static void* reportTimingUserData; - + static bool reportTimingStatsRequest; + static int maxTimePerFrame_ms; + private: SGSubsystemGroup* _group = nullptr; +protected: + TimerStats _timerStats, _lastTimerStats; + double _executionTime; + double _lastExecutionTime; + }; typedef SGSharedPtr SGSubsystemRef; @@ -355,7 +378,7 @@ typedef SGSharedPtr SGSubsystemRef; class SGSubsystemGroup : public SGSubsystem { public: - SGSubsystemGroup (); + SGSubsystemGroup (const std::string &name); virtual ~SGSubsystemGroup (); void init() override; @@ -380,6 +403,7 @@ public: bool remove_subsystem (const std::string &name); virtual bool has_subsystem (const std::string &name) const; + void reportTimingStats(TimerStats *_lastValues) override; /** * Remove all subsystems. */ @@ -477,7 +501,7 @@ public: MAX_GROUPS }; - SGSubsystemMgr (); + SGSubsystemMgr (const std::string &name); virtual ~SGSubsystemMgr (); void init () override; @@ -510,7 +534,8 @@ public: SGSubsystem* get_subsystem(const std::string &name, const std::string& instanceName) const; void reportTiming(); - void setReportTimingCb(void* userData,SGSubsystemTimingCb cb) {reportTimingCb = cb;reportTimingUserData = userData;} + void setReportTimingCb(void* userData, SGSubsystemTimingCb cb) { reportTimingCb = cb; reportTimingUserData = userData; } + void setReportTimingStats(bool v) { reportTimingStatsRequest = v; } /** * @brief set the root property node for this subsystem manager From caea68007e0ef217d29c8b7432a7c712936b3e56 Mon Sep 17 00:00:00 2001 From: Richard Harrison Date: Mon, 11 Feb 2019 08:49:40 +0100 Subject: [PATCH 07/33] Fix subsystem tests --- simgear/structure/subsystem_test.cxx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/simgear/structure/subsystem_test.cxx b/simgear/structure/subsystem_test.cxx index 219a3244..bdd0a6b5 100644 --- a/simgear/structure/subsystem_test.cxx +++ b/simgear/structure/subsystem_test.cxx @@ -90,7 +90,7 @@ class InstrumentGroup : public SGSubsystemGroup { public: static const char* subsystemName() { return "instruments"; } - + InstrumentGroup() : SGSubsystemGroup(InstrumentGroup::subsystemName()) {} virtual ~InstrumentGroup() { } @@ -164,7 +164,7 @@ SGSubsystemMgr::InstancedRegistrant registrant3(SGSubsystemMgr::PO void testRegistrationAndCreation() { - SGSharedPtr manager = new SGSubsystemMgr; + SGSharedPtr manager = new SGSubsystemMgr("TEST1"); auto anotherSub = manager->create(); SG_VERIFY(anotherSub); @@ -181,7 +181,7 @@ void testRegistrationAndCreation() void testAddGetRemove() { - SGSharedPtr manager = new SGSubsystemMgr; + SGSharedPtr manager = new SGSubsystemMgr("TEST1"); auto d = new RecorderDelegate; manager->addDelegate(d); @@ -228,7 +228,7 @@ void testAddGetRemove() void testSubGrouping() { - SGSharedPtr manager = new SGSubsystemMgr; + SGSharedPtr manager = new SGSubsystemMgr("TEST1"); auto d = new RecorderDelegate; manager->addDelegate(d); @@ -299,7 +299,7 @@ void testSubGrouping() void testIncrementalInit() { - SGSharedPtr manager = new SGSubsystemMgr; + SGSharedPtr manager = new SGSubsystemMgr("TEST"); auto d = new RecorderDelegate; manager->addDelegate(d); @@ -347,7 +347,7 @@ void testEmptyGroup() // https://sourceforge.net/p/flightgear/codetickets/2043/ // when an empty group is inited, we skipped setting the state - SGSharedPtr manager = new SGSubsystemMgr; + SGSharedPtr manager = new SGSubsystemMgr("TEST"); auto d = new RecorderDelegate; manager->addDelegate(d); @@ -371,7 +371,7 @@ void testEmptyGroup() void testSuspendResume() { - SGSharedPtr manager = new SGSubsystemMgr; + SGSharedPtr manager = new SGSubsystemMgr("TEST"); auto d = new RecorderDelegate; manager->addDelegate(d); @@ -441,7 +441,7 @@ void testSuspendResume() void testPropertyRoot() { - SGSharedPtr manager = new SGSubsystemMgr; + SGSharedPtr manager = new SGSubsystemMgr("TEST"); SGPropertyNode_ptr props(new SGPropertyNode); manager->set_root_node(props); @@ -467,7 +467,7 @@ void testPropertyRoot() void testAddRemoveAfterInit() { - SGSharedPtr manager = new SGSubsystemMgr; + SGSharedPtr manager = new SGSubsystemMgr("TEST"); auto d = new RecorderDelegate; manager->addDelegate(d); From 34b3c52a288d62779073fc7694344d0658755645 Mon Sep 17 00:00:00 2001 From: James Turner Date: Wed, 13 Feb 2019 12:34:17 +0000 Subject: [PATCH 08/33] Improve HTTP redirect handling, and add test. Ensure we get the final status code for the request after redirecting. --- simgear/io/HTTPClient.cxx | 14 ++++++++++++++ simgear/io/HTTPRequest.cxx | 10 ++++++++++ simgear/io/HTTPRequest.hxx | 2 +- simgear/io/test_HTTP.cxx | 36 +++++++++++++++++++++++++++++++++++- simgear/io/test_HTTP.hxx | 6 ++---- 5 files changed, 62 insertions(+), 6 deletions(-) diff --git a/simgear/io/HTTPClient.cxx b/simgear/io/HTTPClient.cxx index 0c31354b..0adf0be8 100644 --- a/simgear/io/HTTPClient.cxx +++ b/simgear/io/HTTPClient.cxx @@ -476,12 +476,26 @@ size_t Client::requestReadCallback(char *ptr, size_t size, size_t nmemb, void *u return actualBytes; } +bool isRedirectStatus(int code) +{ + return ((code >= 300) && (code < 400)); +} + size_t Client::requestHeaderCallback(char *rawBuffer, size_t size, size_t nitems, void *userdata) { size_t byteSize = size * nitems; Request* req = static_cast(userdata); std::string h = strutils::simplify(std::string(rawBuffer, byteSize)); + if (req->readyState() >= HTTP::Request::HEADERS_RECEIVED) { + // this can happen with chunked transfers (secondary chunks) + // or redirects + if (isRedirectStatus(req->responseCode())) { + req->responseStart(h); + return byteSize; + } + } + if (req->readyState() == HTTP::Request::OPENED) { req->responseStart(h); return byteSize; diff --git a/simgear/io/HTTPRequest.cxx b/simgear/io/HTTPRequest.cxx index 294d2f0c..7266d780 100644 --- a/simgear/io/HTTPRequest.cxx +++ b/simgear/io/HTTPRequest.cxx @@ -328,6 +328,16 @@ unsigned int Request::responseLength() const return _responseLength; } +//------------------------------------------------------------------------------ +void Request::setSuccess(int code) +{ + _responseStatus = code; + _responseReason.clear(); + if( !isComplete() ) { + setReadyState(DONE); + } +} + //------------------------------------------------------------------------------ void Request::setFailure(int code, const std::string& reason) { diff --git a/simgear/io/HTTPRequest.hxx b/simgear/io/HTTPRequest.hxx index 0def0888..9ba8db3e 100644 --- a/simgear/io/HTTPRequest.hxx +++ b/simgear/io/HTTPRequest.hxx @@ -224,7 +224,7 @@ protected: virtual void onAlways(); void setFailure(int code, const std::string& reason); - + void setSuccess(int code); private: friend class Client; friend class Connection; diff --git a/simgear/io/test_HTTP.cxx b/simgear/io/test_HTTP.cxx index 3bf9947d..ccef3f9e 100644 --- a/simgear/io/test_HTTP.cxx +++ b/simgear/io/test_HTTP.cxx @@ -273,7 +273,23 @@ public: d << "\r\n"; // final CRLF to terminate the headers d << contentStr; push(d.str().c_str()); - + } else if (path == "/test_redirect") { + string contentStr("See Here"); + stringstream d; + d << "HTTP/1.1 " << 302 << " " << "Found" << "\r\n"; + d << "Location:" << " http://localhost:2000/was_redirected" << "\r\n"; + d << "Content-Length:" << contentStr.size() << "\r\n"; + d << "\r\n"; // final CRLF to terminate the headers + d << contentStr; + push(d.str().c_str()); + } else if (path == "/was_redirected") { + string contentStr(BODY1); + stringstream d; + d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n"; + d << "Content-Length:" << contentStr.size() << "\r\n"; + d << "\r\n"; // final CRLF to terminate the headers + d << contentStr; + push(d.str().c_str()); } else { TestServerChannel::processRequestHeaders(); } @@ -773,6 +789,24 @@ cout << "testing proxy close" << endl; SG_CHECK_EQUAL(tr2->bodyData, string(BODY1)); SG_CHECK_EQUAL(tr2->responseBytesReceived(), strlen(BODY1)); } + + { + cout << "redirect test" << endl; + // redirect test + testServer.disconnectAll(); + cl.clearAllConnections(); + + TestRequest* tr = new TestRequest("http://localhost:2000/test_redirect"); + HTTP::Request_ptr own(tr); + cl.makeRequest(tr); + + waitForComplete(&cl, tr); + SG_CHECK_EQUAL(tr->responseCode(), 200); + SG_CHECK_EQUAL(tr->responseReason(), string("OK")); + SG_CHECK_EQUAL(tr->responseLength(), strlen(BODY1)); + SG_CHECK_EQUAL(tr->responseBytesReceived(), strlen(BODY1)); + SG_CHECK_EQUAL(tr->bodyData, string(BODY1)); + } cout << "all tests passed ok" << endl; return EXIT_SUCCESS; diff --git a/simgear/io/test_HTTP.hxx b/simgear/io/test_HTTP.hxx index 3cb925c0..f8dde98c 100644 --- a/simgear/io/test_HTTP.hxx +++ b/simgear/io/test_HTTP.hxx @@ -30,7 +30,6 @@ public: virtual ~TestServerChannel() { - std::cerr << "dtor test server channel" << std::endl; } virtual void collectIncomingData(const char* s, int n) @@ -139,8 +138,8 @@ public: void sendErrorResponse(int code, bool close, std::string content) { - std::cerr << "sending error " << code << " for " << path << std::endl; - std::cerr << "\tcontent:" << content << std::endl; + // std::cerr << "sending error " << code << " for " << path << std::endl; + // std::cerr << "\tcontent:" << content << std::endl; std::stringstream headerData; headerData << "HTTP/1.1 " << code << " " << reasonForCode(code) << "\r\n"; @@ -168,7 +167,6 @@ public: virtual void handleClose (void) { - std::cerr << "channel close" << std::endl; NetBufferChannel::handleClose(); } From 6197098541eceecdb0dcfe8a58b15f0d0773c391 Mon Sep 17 00:00:00 2001 From: James Turner Date: Wed, 13 Feb 2019 12:49:21 +0000 Subject: [PATCH 09/33] Guard against a potential race init-ing libCurl --- simgear/io/HTTPClient.cxx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/simgear/io/HTTPClient.cxx b/simgear/io/HTTPClient.cxx index 0adf0be8..d5c32af7 100644 --- a/simgear/io/HTTPClient.cxx +++ b/simgear/io/HTTPClient.cxx @@ -48,6 +48,8 @@ #include #include #include +#include +#include #if defined( HAVE_VERSION_H ) && HAVE_VERSION_H #include "version.h" @@ -123,6 +125,9 @@ Client::Client() : setUserAgent("SimGear-" SG_STRINGIZE(SIMGEAR_VERSION)); static bool didInitCurlGlobal = false; + static SGMutex initMutex; + + SGGuard g(initMutex); if (!didInitCurlGlobal) { curl_global_init(CURL_GLOBAL_ALL); didInitCurlGlobal = true; From 98215e3dbd66e53fcccbf347e3e5a5eb51e54afb Mon Sep 17 00:00:00 2001 From: Stuart Buchanan Date: Thu, 14 Feb 2019 22:13:54 +0000 Subject: [PATCH 10/33] Improve error messages for Nasal prop references. --- simgear/props/props.cxx | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/simgear/props/props.cxx b/simgear/props/props.cxx index 8b599632..482d54ce 100644 --- a/simgear/props/props.cxx +++ b/simgear/props/props.cxx @@ -118,7 +118,7 @@ parse_name (const SGPropertyNode *node, const Range &path) } else { std::string err = "'"; err.push_back(*i); - err.append("' found in propertyname after '"+node->getNameString()+"'"); + err.append("' found in propertyname after '"+node->getPath()+"'"); err.append("\nname may contain only ._- and alphanumeric characters"); throw err; } @@ -130,7 +130,7 @@ parse_name (const SGPropertyNode *node, const Range &path) if (path.begin() == i) { std::string err = "'"; err.push_back(*i); - err.append("' found in propertyname after '"+node->getNameString()+"'"); + err.append("' found in propertyname after '"+node->getPath()+"'"); err.append("\nname must begin with alpha or '_'"); throw err; } @@ -536,7 +536,7 @@ find_node (SGPropertyNode * current, using namespace boost; typedef split_iterator::type> PathSplitIterator; - + PathSplitIterator itr = make_split_iterator(path, first_finder("/", is_equal())); if (*path.begin() == '/') @@ -874,7 +874,7 @@ SGPropertyNode::SGPropertyNode (const SGPropertyNode &node) } switch (_type) { case props::BOOL: - set_bool(node.get_bool()); + set_bool(node.get_bool()); break; case props::INT: set_int(node.get_int()); @@ -967,7 +967,7 @@ SGPropertyNode::alias (SGPropertyNode * target) for (auto p = target; p; p = ((p->_type == props::ALIAS) ? p->_value.alias : nullptr)) { if (p == this) return false; } - + clearValue(); get(target); _value.alias = target; @@ -993,7 +993,7 @@ SGPropertyNode::alias (SGPropertyNode * target) else if (_tied) { - SG_LOG(SG_GENERAL, SG_ALERT, "alias(): " << getPath() << + SG_LOG(SG_GENERAL, SG_ALERT, "alias(): " << getPath() << " is a tied property. It cannot alias " << target->getPath() << "."); } @@ -1316,7 +1316,7 @@ SGPropertyNode::getType () const } -bool +bool SGPropertyNode::getBoolValue () const { // Shortcut for common case @@ -1349,7 +1349,7 @@ SGPropertyNode::getBoolValue () const } } -int +int SGPropertyNode::getIntValue () const { // Shortcut for common case @@ -1382,7 +1382,7 @@ SGPropertyNode::getIntValue () const } } -long +long SGPropertyNode::getLongValue () const { // Shortcut for common case @@ -1415,7 +1415,7 @@ SGPropertyNode::getLongValue () const } } -float +float SGPropertyNode::getFloatValue () const { // Shortcut for common case @@ -1448,7 +1448,7 @@ SGPropertyNode::getFloatValue () const } } -double +double SGPropertyNode::getDoubleValue () const { // Shortcut for common case @@ -2574,7 +2574,7 @@ template<> std::ostream& SGRawBase::printOn(std::ostream& stream) const { const SGVec4d vec - = static_cast*>(this)->getValue(); + = static_cast*>(this)->getValue(); for (int i = 0; i < 4; ++i) { stream << vec[i]; if (i < 3) From f076c43b90d9615c8c94622d30de022a0a84ead5 Mon Sep 17 00:00:00 2001 From: Stuart Buchanan Date: Thu, 14 Feb 2019 22:15:38 +0000 Subject: [PATCH 11/33] Improve parsing of state machines State machines will now throw exceptions if errors are encountered when loading the XML file. This includes: - Unknown or states referenced by transitions - States and transitions without elements - Transitions without or elements - State machines with fewer than two states Previously the state machine would load and then crash when hitting such malformed states or transitions. Now it will refuse to load, while providing useful error messages as to the problem. --- simgear/structure/StateMachine.cxx | 174 ++++++++++++++++++----------- 1 file changed, 111 insertions(+), 63 deletions(-) diff --git a/simgear/structure/StateMachine.cxx b/simgear/structure/StateMachine.cxx index 2bff0f3f..e29fefb1 100644 --- a/simgear/structure/StateMachine.cxx +++ b/simgear/structure/StateMachine.cxx @@ -20,7 +20,7 @@ */ #include - + #include "StateMachine.hxx" #include @@ -32,13 +32,13 @@ #include #include #include - + namespace simgear { typedef std::vector StatePtrVec; -static void readBindingList(SGPropertyNode* desc, const std::string& name, +static void readBindingList(SGPropertyNode* desc, const std::string& name, SGPropertyNode* root, SGBindingList& result) { for (auto b : desc->getChildren(name)) { @@ -54,8 +54,8 @@ class StateMachine::State::StatePrivate { public: std::string _name; - SGBindingList _updateBindings, - _entryBindings, + SGBindingList _updateBindings, + _entryBindings, _exitBindings; }; @@ -71,14 +71,14 @@ public: bool _excludeTarget; SGSharedPtr _condition; }; - + /////////////////////////////////////////////////////////////////////////// class StateMachine::StateMachinePrivate : public SGPropertyChangeListener { public: StateMachinePrivate(StateMachine* p) : _p(p) { } - + void computeEligibleTransitions() { _eligible.clear(); @@ -88,7 +88,7 @@ public: } } } - + StateMachine* _p; bool _initialised; State_ptr _currentState; @@ -96,14 +96,14 @@ public: std::vector _transitions; std::vector _eligible; SGTimeStamp _timeInState; - + bool _listenerLockout; ///< block our listener when self-updating props virtual void valueChanged(SGPropertyNode* changed) { if (_listenerLockout) { return; } - + if (changed == _currentStateIndex) { State_ptr s = _p->stateByIndex(changed->getIntValue()); _p->changeToState(s); @@ -111,7 +111,7 @@ public: _p->changeToStateName(changed->getStringValue()); } } - + // exposed properties SGPropertyNode_ptr _root; SGPropertyNode_ptr _currentStateIndex; @@ -144,12 +144,12 @@ void StateMachine::State::update() void StateMachine::State::fireEntryBindings() { fireBindingList(d->_entryBindings); -} +} void StateMachine::State::fireExitBindings() { fireBindingList(d->_exitBindings); -} +} void StateMachine::State::addUpdateBinding(SGBinding* aBinding) { @@ -184,38 +184,38 @@ StateMachine::Transition::~Transition() StateMachine::State* StateMachine::Transition::target() const { return d->_target; -} +} void StateMachine::Transition::addSourceState(State* aSource) { if (aSource == d->_target) { // should this be disallowed outright? SG_LOG(SG_GENERAL, SG_WARN, d->_name << ": adding target state as source"); } - + d->_sourceStates.insert(aSource); -} +} bool StateMachine::Transition::applicableForState(State* aCurrent) const { if (d->_excludeTarget && (aCurrent == d->_target)) { return false; } - + if (d->_sourceStates.empty()) { return true; } return d->_sourceStates.count(aCurrent); -} - +} + bool StateMachine::Transition::evaluate() const { return d->_condition->test(); -} +} void StateMachine::Transition::fireBindings() { fireBindingList(d->_bindings); -} +} std::string StateMachine::Transition::name() const { @@ -231,12 +231,12 @@ void StateMachine::Transition::addBinding(SGBinding* aBinding) { d->_bindings.push_back(aBinding); } - + void StateMachine::Transition::setExcludeTarget(bool aExclude) { d->_excludeTarget = aExclude; } - + /////////////////////////////////////////////////////////////////////////// StateMachine::StateMachine() : @@ -249,7 +249,7 @@ StateMachine::StateMachine() : StateMachine::~StateMachine() { - + } void StateMachine::init() @@ -257,23 +257,23 @@ void StateMachine::init() if (d->_initialised) { return; } - + if (d->_states.empty()) { throw sg_range_exception("StateMachine::init: no states defined"); } - + d->_currentStateIndex = d->_root->getChild("current-index", 0, true); d->_currentStateIndex->setIntValue(0); - + d->_currentStateName = d->_root->getChild("current-name", 0, true); d->_currentStateName->setStringValue(""); - + d->_currentStateIndex->addChangeListener(d.get()); d->_currentStateName->addChangeListener(d.get()); - + d->_timeInStateProp = d->_root->getChild("elapsed-time-msec", 0, true); d->_timeInStateProp->setIntValue(0); - + // TODO go to default state if found innerChangeState(d->_states[0], NULL); d->_initialised = true; @@ -283,20 +283,23 @@ void StateMachine::shutdown() { d->_currentStateIndex->removeChangeListener(d.get()); d->_currentStateName->removeChangeListener(d.get()); - + } void StateMachine::innerChangeState(State_ptr aState, Transition_ptr aTrans) { if (d->_currentState) { d->_currentState->fireExitBindings(); + SG_LOG(SG_GENERAL, SG_INFO, "Changing from state " << d->_currentState->name() << " to state:" << aState->name()); + } else { + SG_LOG(SG_GENERAL, SG_INFO, "Initializing to state:" << aState->name()); } - -// fire bindings before we change the state, hmmmm + +// fire bindings before we change the state, hmmmm if (aTrans) { aTrans->fireBindings(); } - + // update our private state and properties d->_listenerLockout = true; d->_currentState = aState; @@ -305,11 +308,11 @@ void StateMachine::innerChangeState(State_ptr aState, Transition_ptr aTrans) d->_currentStateIndex->setIntValue(indexOfState(aState)); d->_timeInStateProp->setIntValue(0); d->_listenerLockout = false; - + // fire bindings d->_currentState->fireEntryBindings(); d->_currentState->update(); - + d->computeEligibleTransitions(); } @@ -319,11 +322,11 @@ void StateMachine::changeToState(State_ptr aState, bool aOnlyIfDifferent) if (std::find(d->_states.begin(), d->_states.end(), aState) == d->_states.end()) { throw sg_exception("Requested change to state not in machine"); } - + if (aOnlyIfDifferent && (aState == d->_currentState)) { return; } - + innerChangeState(aState, NULL); } @@ -333,7 +336,7 @@ void StateMachine::changeToStateName(const std::string& aName, bool aOnlyIfDiffe if (!st) { throw sg_range_exception("unknown state:" + aName); } - + changeToState(st, aOnlyIfDifferent); } @@ -341,7 +344,7 @@ StateMachine::State_ptr StateMachine::state() const { return d->_currentState; } - + SGPropertyNode* StateMachine::root() { return d->_root; @@ -352,27 +355,27 @@ void StateMachine::update(double aDt) // do this first, for triggers which depend on time in current state // (spring-loaded transitions) d->_timeInStateProp->setIntValue(d->_timeInState.elapsedMSec()); - + Transition_ptr trigger; - + for (auto trans : d->_eligible) { if (trans->evaluate()) { if (trigger != Transition_ptr()) { - SG_LOG(SG_GENERAL, SG_WARN, "ambiguous transitions! " + SG_LOG(SG_GENERAL, SG_WARN, "ambiguous transitions! " << trans->name() << " or " << trigger->name()); } - + trigger = trans; } } - + if (trigger != Transition_ptr()) { SG_LOG(SG_GENERAL, SG_DEBUG, "firing transition:" << trigger->name()); innerChangeState(trigger->target(), trigger); } - + d->_currentState->update(); -} +} StateMachine::State_ptr StateMachine::findStateByName(const std::string& aName) const { @@ -381,7 +384,7 @@ StateMachine::State_ptr StateMachine::findStateByName(const std::string& aName) return sp; } } - + SG_LOG(SG_GENERAL, SG_WARN, "unknown state:" << aName); return State_ptr(); } @@ -391,17 +394,17 @@ StateMachine::State_ptr StateMachine::stateByIndex(unsigned int aIndex) const if (aIndex >= d->_states.size()) { throw sg_range_exception("invalid state index, out of bounds"); } - + return d->_states[aIndex]; } - + int StateMachine::indexOfState(State_ptr aState) const { StatePtrVec::const_iterator it = std::find(d->_states.begin(), d->_states.end(), aState); if (it == d->_states.end()) { return -1; } - + return it - d->_states.begin(); } @@ -410,7 +413,7 @@ StateMachine::State_ptr StateMachine::createState(const std::string& aName) if (findStateByName(aName) != NULL) { throw sg_range_exception("duplicate state name"); } - + State_ptr st = new State(aName); addState(st); return st; @@ -431,38 +434,83 @@ void StateMachine::initFromPlist(SGPropertyNode* desc, SGPropertyNode* root) d->_root = root->getNode(path, 0, true); assert(d->_root); } - + + int stateCount = 0; for (auto stateDesc : desc->getChildren("state")) { + stateCount++; std::string nm = stateDesc->getStringValue("name"); + + if (nm.empty()) { + SG_LOG(SG_GENERAL, SG_ALERT, "No name found for state in branch " << path); + throw sg_exception("No name element in state"); + } + State_ptr st(new State(nm)); - + readBindingList(stateDesc, "enter", root, st->d->_entryBindings); readBindingList(stateDesc, "update", root, st->d->_updateBindings); readBindingList(stateDesc, "exit", root, st->d->_exitBindings); - + addState(st); } // of states iteration - + + if (stateCount < 2) { + SG_LOG(SG_GENERAL, SG_ALERT, "Fewer than two state elements found in branch " << path); + throw sg_exception("Fewer than two state elements found."); + } + for (auto tDesc : desc->getChildren("transition")) { std::string nm = tDesc->getStringValue("name"); - State_ptr target = findStateByName(tDesc->getStringValue("target")); - + std::string target_id = tDesc->getStringValue("target"); + + if (nm.empty()) { + SG_LOG(SG_GENERAL, SG_ALERT, "No name found for transition in branch " << path); + throw sg_exception("No name element in transition"); + } + + if (target_id.empty()) { + SG_LOG(SG_GENERAL, SG_ALERT, "No target element in transition " + << nm << " in state branch " << path); + throw sg_exception("No target element in transition"); + } + + State_ptr target = findStateByName(target_id); + + if (target == NULL) { + SG_LOG(SG_GENERAL, SG_ALERT, "Unknown target state " << target_id << " in transition " + << nm << " in state branch " << path); + throw sg_exception("No condition element in transition"); + } + + if (tDesc->getChild("condition") == NULL) { + SG_LOG(SG_GENERAL, SG_ALERT, "No condition element in transition " + << nm << " in state branch " << path); + throw sg_exception("No condition element in transition"); + } + SGCondition* cond = sgReadCondition(root, tDesc->getChild("condition")); - + Transition_ptr t(new Transition(nm, target)); t->setTriggerCondition(cond); - + t->setExcludeTarget(tDesc->getBoolValue("exclude-target", true)); for (auto src : tDesc->getChildren("source")) { State_ptr srcState = findStateByName(src->getStringValue()); + + if (srcState == NULL) { + SG_LOG(SG_GENERAL, SG_ALERT, "Unknown source state " << src->getStringValue() << " in transition " + << nm << " in state branch " << path); + throw sg_exception("No condition element in transition"); + } + t->addSourceState(srcState); } - + readBindingList(tDesc, "binding", root, t->d->_bindings); - + addTransition(t); } // of states iteration - + init(); } From dd240116b1c547cbcf2bcc18c81d787ba7ba315d Mon Sep 17 00:00:00 2001 From: Richard Harrison Date: Tue, 19 Feb 2019 08:59:54 +0100 Subject: [PATCH 12/33] Compositor compilation fixes for OSG 3.6 and Win32 --- simgear/scene/viewer/ClusteredForward.cxx | 16 +++++++++++----- simgear/scene/viewer/CompositorBuffer.cxx | 11 ++++++----- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/simgear/scene/viewer/ClusteredForward.cxx b/simgear/scene/viewer/ClusteredForward.cxx index 9235a71f..e2b50180 100644 --- a/simgear/scene/viewer/ClusteredForward.cxx +++ b/simgear/scene/viewer/ClusteredForward.cxx @@ -72,8 +72,8 @@ ClusteredForwardDrawCallback::operator()(osg::RenderInfo &renderInfo) const if (!_initialized) { // Create and associate the light grid 3D texture _light_grid->allocateImage(n_htiles, n_vtiles, 1, - GL_RGB_INTEGER, GL_UNSIGNED_SHORT); - _light_grid->setInternalTextureFormat(GL_RGB16UI); + GL_RGB_INTEGER_EXT, GL_UNSIGNED_SHORT); + _light_grid->setInternalTextureFormat(GL_RGB16UI_EXT); osg::ref_ptr light_grid_tex = new osg::Texture3D; light_grid_tex->setResizeNonPowerOfTwoHint(false); @@ -88,7 +88,7 @@ ClusteredForwardDrawCallback::operator()(osg::RenderInfo &renderInfo) const 10, light_grid_tex.get(), osg::StateAttribute::ON); // Create and associate the light indices TBO - _light_indices->allocateImage(4096, 1, 1, GL_RED_INTEGER, GL_UNSIGNED_SHORT); + _light_indices->allocateImage(4096, 1, 1, GL_RED_INTEGER_EXT, GL_UNSIGNED_SHORT); osg::ref_ptr light_indices_tbo = new osg::TextureBuffer; @@ -103,10 +103,16 @@ ClusteredForwardDrawCallback::operator()(osg::RenderInfo &renderInfo) const new osg::UniformBufferObject; _light_data->setBufferObject(light_data_ubo.get()); - osg::ref_ptr light_data_ubb = +#if OSG_VERSION_LESS_THAN(3,6,0) + osg::ref_ptr light_data_ubb = new osg::UniformBufferBinding(0, light_data_ubo.get(), 0, MAX_POINT_LIGHTS * 8 * sizeof(GLfloat)); - light_data_ubb->setDataVariance(osg::Object::DYNAMIC); +#else + osg::ref_ptr light_data_ubb = + new osg::UniformBufferBinding(0, _light_data.get(), + 0, MAX_POINT_LIGHTS * 8 * sizeof(GLfloat)); +#endif +light_data_ubb->setDataVariance(osg::Object::DYNAMIC); camera->getOrCreateStateSet()->setAttribute( light_data_ubb.get(), osg::StateAttribute::ON); diff --git a/simgear/scene/viewer/CompositorBuffer.cxx b/simgear/scene/viewer/CompositorBuffer.cxx index b52b5530..183e4de5 100644 --- a/simgear/scene/viewer/CompositorBuffer.cxx +++ b/simgear/scene/viewer/CompositorBuffer.cxx @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -43,17 +44,17 @@ struct BufferFormat { PropStringMap buffer_format_map { {"rgb8", {GL_RGB8, GL_RGBA, GL_UNSIGNED_BYTE}}, {"rgba8", {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE}}, - {"rgb16f", {GL_RGB16F, GL_RGBA, GL_FLOAT}}, - {"rgb32f", {GL_RGB32F, GL_RGBA, GL_FLOAT}}, - {"rgba16f", {GL_RGBA16F, GL_RGBA, GL_FLOAT}}, - {"rgba32f", {GL_RGBA32F, GL_RGBA, GL_FLOAT}}, + {"rgb16f", {GL_RGB16F_ARB, GL_RGBA, GL_FLOAT}}, + {"rgb32f", {GL_RGB32F_ARB, GL_RGBA, GL_FLOAT}}, + {"rgba16f", {GL_RGBA16F_ARB, GL_RGBA, GL_FLOAT}}, + {"rgba32f", {GL_RGBA32F_ARB, GL_RGBA, GL_FLOAT}}, {"r32f", {GL_R32F, GL_RED, GL_FLOAT}}, {"rg32f", {GL_RG32F, GL_RG, GL_FLOAT}}, {"depth16", {GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}}, {"depth24", {GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT, GL_FLOAT}}, {"depth32", {GL_DEPTH_COMPONENT32, GL_DEPTH_COMPONENT, GL_FLOAT}}, {"depth32f", {GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT}}, - {"depth-stencil", {GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_FLOAT}} + {"depth-stencil", { GL_DEPTH24_STENCIL8_EXT, GL_DEPTH_STENCIL_EXT, GL_FLOAT}} }; PropStringMap wrap_mode_map = { From 31c9df665d11d120bd944a40792128594ffd4fa4 Mon Sep 17 00:00:00 2001 From: Richard Harrison Date: Tue, 19 Feb 2019 15:11:04 +0100 Subject: [PATCH 13/33] Fix compile error related to missing osg version include (< 3.6) For some reason this compiles on 3.6. --- simgear/scene/viewer/ClusteredForward.cxx | 1 + 1 file changed, 1 insertion(+) diff --git a/simgear/scene/viewer/ClusteredForward.cxx b/simgear/scene/viewer/ClusteredForward.cxx index e2b50180..6e5f4098 100644 --- a/simgear/scene/viewer/ClusteredForward.cxx +++ b/simgear/scene/viewer/ClusteredForward.cxx @@ -21,6 +21,7 @@ #include #include #include +#include namespace simgear { namespace compositor { From 72f3a784de9417ce7ef6a38c153a6e19b63391c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Garc=C3=ADa=20Li=C3=B1=C3=A1n?= Date: Wed, 20 Feb 2019 03:05:55 +0100 Subject: [PATCH 14/33] Compositor: Added support for static branching and tags to be able to configure the Compositor at startup. --- simgear/scene/viewer/CMakeLists.txt | 3 +- simgear/scene/viewer/Compositor.cxx | 14 +++-- simgear/scene/viewer/CompositorBuffer.cxx | 46 +++++++++----- simgear/scene/viewer/CompositorPass.cxx | 11 ++-- simgear/scene/viewer/CompositorPass.hxx | 4 -- simgear/scene/viewer/CompositorUtil.cxx | 62 +++++++++++++++++++ ...ompositorCommon.hxx => CompositorUtil.hxx} | 16 ++++- 7 files changed, 120 insertions(+), 36 deletions(-) create mode 100644 simgear/scene/viewer/CompositorUtil.cxx rename simgear/scene/viewer/{CompositorCommon.hxx => CompositorUtil.hxx} (78%) diff --git a/simgear/scene/viewer/CMakeLists.txt b/simgear/scene/viewer/CMakeLists.txt index df57469b..d26297fc 100644 --- a/simgear/scene/viewer/CMakeLists.txt +++ b/simgear/scene/viewer/CMakeLists.txt @@ -1,9 +1,9 @@ set(HEADERS ClusteredForward.hxx Compositor.hxx - CompositorCommon.hxx CompositorBuffer.hxx CompositorPass.hxx + CompositorUtil.hxx ) set(SOURCES @@ -11,6 +11,7 @@ set(SOURCES Compositor.cxx CompositorBuffer.cxx CompositorPass.cxx + CompositorUtil.cxx ) simgear_scene_component(viewer scene/viewer "${SOURCES}" "${HEADERS}") diff --git a/simgear/scene/viewer/Compositor.cxx b/simgear/scene/viewer/Compositor.cxx index 8058d0d3..dd267504 100644 --- a/simgear/scene/viewer/Compositor.cxx +++ b/simgear/scene/viewer/Compositor.cxx @@ -27,8 +27,10 @@ #include #include #include -#include #include +#include + +#include "CompositorUtil.hxx" namespace simgear { namespace compositor { @@ -45,6 +47,8 @@ Compositor::create(osg::View *view, // Read all buffers first so passes can use them PropertyList p_buffers = property_list->getChildren("buffer"); for (auto const &p_buffer : p_buffers) { + if (!checkConditional(p_buffer)) + continue; const std::string &buffer_name = p_buffer->getStringValue("name"); if (buffer_name.empty()) { SG_LOG(SG_INPUT, SG_ALERT, "Compositor::build: Buffer requires " @@ -58,6 +62,8 @@ Compositor::create(osg::View *view, // Read passes PropertyList p_passes = property_list->getChildren("pass"); for (auto const &p_pass : p_passes) { + if (!checkConditional(p_pass)) + continue; Pass *pass = buildPass(compositor.get(), p_pass); if (pass) compositor->addPass(pass); @@ -189,11 +195,7 @@ Compositor::setCameraCullMasks(osg::Node::NodeMask nm) for (const auto &pass : _passes) { osg::Camera *camera = pass->camera; osg::Node::NodeMask pass_cm = nm; - // Disable traversal of the scene graph if the pass isn't enabled - if (!pass->isEnabled()) - pass_cm &= 0x0; - else - pass_cm &= pass->cull_mask; + pass_cm &= pass->cull_mask; camera->setCullMask(pass_cm); camera->setCullMaskLeft(pass_cm); diff --git a/simgear/scene/viewer/CompositorBuffer.cxx b/simgear/scene/viewer/CompositorBuffer.cxx index 183e4de5..2acb6be9 100644 --- a/simgear/scene/viewer/CompositorBuffer.cxx +++ b/simgear/scene/viewer/CompositorBuffer.cxx @@ -30,7 +30,7 @@ #include #include "Compositor.hxx" -#include "CompositorCommon.hxx" +#include "CompositorUtil.hxx" namespace simgear { namespace compositor { @@ -103,23 +103,37 @@ buildBuffer(Compositor *compositor, const SGPropertyNode *node) osg::ref_ptr buffer = new Buffer; osg::Texture *texture; - int width; - if (node->getStringValue("width") == std::string("screen")) { - float w_scale = node->getFloatValue("screen-width-scale", 1.0f); - buffer->width_scale = w_scale; - width = w_scale * compositor->getViewport()->width(); - } else { - width = node->getIntValue("width"); + int width = 0; + const SGPropertyNode *p_width = getPropertyChild(node, "width"); + if (p_width) { + if (p_width->getStringValue() == std::string("screen")) { + buffer->width_scale = 1.0f; + const SGPropertyNode *p_w_scale = getPropertyChild(node, "screen-width-scale"); + if (p_w_scale) + buffer->width_scale = p_w_scale->getFloatValue(); + width = buffer->width_scale * compositor->getViewport()->width(); + } else { + width = p_width->getIntValue(); + } } - int height; - if (node->getStringValue("height") == std::string("screen")) { - float h_scale = node->getFloatValue("screen-height-scale", 1.0f); - buffer->height_scale = h_scale; - height = h_scale * compositor->getViewport()->height(); - } else { - height = node->getIntValue("height"); + int height = 0; + const SGPropertyNode *p_height = getPropertyChild(node, "height"); + if (p_height) { + if (p_height->getStringValue() == std::string("screen")) { + buffer->height_scale = 1.0f; + const SGPropertyNode *p_h_scale = getPropertyChild(node, "screen-height-scale"); + if (p_h_scale) + buffer->height_scale = p_h_scale->getFloatValue(); + height = buffer->height_scale * compositor->getViewport()->height(); + } else { + height = p_height->getIntValue(); + } } - int depth = node->getIntValue("depth"); + int depth = 0; + const SGPropertyNode *p_depth = getPropertyChild(node, "depth"); + if (p_depth) + depth = p_depth->getIntValue(); + if (type == "1d") { osg::Texture1D *tex1D = new osg::Texture1D; tex1D->setTextureWidth(width); diff --git a/simgear/scene/viewer/CompositorPass.cxx b/simgear/scene/viewer/CompositorPass.cxx index d62a8a90..96f6e9e0 100644 --- a/simgear/scene/viewer/CompositorPass.cxx +++ b/simgear/scene/viewer/CompositorPass.cxx @@ -24,14 +24,13 @@ #include #include -#include #include #include #include #include "ClusteredForward.hxx" #include "Compositor.hxx" -#include "CompositorCommon.hxx" +#include "CompositorUtil.hxx" namespace simgear { namespace compositor { @@ -65,10 +64,6 @@ PassBuilder::build(Compositor *compositor, const SGPropertyNode *root) } pass->type = root->getStringValue("type"); - const SGPropertyNode *condition = root->getChild("condition"); - if (condition) - pass->condition = sgReadCondition(getPropertyRoot(), condition); - std::string eff_override_file = root->getStringValue("effect-override"); if (!eff_override_file.empty()) pass->effect_override = makeEffect(eff_override_file, true, 0); @@ -115,6 +110,8 @@ PassBuilder::build(Compositor *compositor, const SGPropertyNode *root) PropertyList p_bindings = root->getChildren("binding"); for (auto const &p_binding : p_bindings) { + if (!checkConditional(p_binding)) + continue; try { std::string buffer_name = p_binding->getStringValue("buffer"); if (buffer_name.empty()) @@ -190,6 +187,8 @@ PassBuilder::build(Compositor *compositor, const SGPropertyNode *root) } for (auto const &p_attachment : p_attachments) { + if (!checkConditional(p_attachment)) + continue; try { std::string buffer_name = p_attachment->getStringValue("buffer"); if (buffer_name.empty()) diff --git a/simgear/scene/viewer/CompositorPass.hxx b/simgear/scene/viewer/CompositorPass.hxx index cb59a83c..fae90d29 100644 --- a/simgear/scene/viewer/CompositorPass.hxx +++ b/simgear/scene/viewer/CompositorPass.hxx @@ -24,7 +24,6 @@ #include #include -#include #include namespace simgear { @@ -51,12 +50,9 @@ struct Pass : public osg::Referenced { viewport_width_scale(0.0f), viewport_height_scale(0.0f) {} - bool isEnabled() const { return (condition == 0) || condition->test(); } - std::string name; std::string type; osg::ref_ptr camera; - SGSharedPtr condition; /** If null, there is no effect override for this pass. */ osg::ref_ptr effect_override; bool useMastersSceneData; diff --git a/simgear/scene/viewer/CompositorUtil.cxx b/simgear/scene/viewer/CompositorUtil.cxx new file mode 100644 index 00000000..9595e7f3 --- /dev/null +++ b/simgear/scene/viewer/CompositorUtil.cxx @@ -0,0 +1,62 @@ +// Copyright (C) 2018 Fernando García Liñán +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Library General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Library General Public License for more details. +// +// You should have received a copy of the GNU Library General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + +#include +#include +#include + +#include "CompositorUtil.hxx" + +namespace simgear { +namespace compositor { + +bool +checkConditional(const SGPropertyNode *node) +{ + const SGPropertyNode *p_condition = node->getChild("condition"); + if (!p_condition) + return true; + SGSharedPtr condition = + sgReadCondition(getPropertyRoot(), p_condition); + return !condition || condition->test(); +} + +const SGPropertyNode * +getPropertyNode(const SGPropertyNode *prop) +{ + if (!prop) + return 0; + if (prop->nChildren() > 0) { + const SGPropertyNode *propertyProp = prop->getChild("property"); + if (!propertyProp) + return prop; + return getPropertyRoot()->getNode(propertyProp->getStringValue()); + } + return prop; +} + +const SGPropertyNode * +getPropertyChild(const SGPropertyNode *prop, + const char *name) +{ + const SGPropertyNode *child = prop->getChild(name); + if (!child) + return 0; + return getPropertyNode(child); +} + +} // namespace compositor +} // namespace simgear diff --git a/simgear/scene/viewer/CompositorCommon.hxx b/simgear/scene/viewer/CompositorUtil.hxx similarity index 78% rename from simgear/scene/viewer/CompositorCommon.hxx rename to simgear/scene/viewer/CompositorUtil.hxx index 17d519ae..d5385908 100644 --- a/simgear/scene/viewer/CompositorCommon.hxx +++ b/simgear/scene/viewer/CompositorUtil.hxx @@ -14,8 +14,8 @@ // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA -#ifndef SG_COMPOSITOR_COMMON_HXX -#define SG_COMPOSITOR_COMMON_HXX +#ifndef SG_COMPOSITOR_UTIL_HXX +#define SG_COMPOSITOR_UTIL_HXX #include @@ -56,7 +56,17 @@ bool findPropString(const SGPropertyNode *parent, return false; } +/** + * Check if node should be enabled based on a condition tag. + * If no condition tag is found inside or it is malformed, it will be enabled. + */ +bool checkConditional(const SGPropertyNode *node); + +const SGPropertyNode *getPropertyNode(const SGPropertyNode *prop); +const SGPropertyNode *getPropertyChild(const SGPropertyNode *prop, + const char *name); + } // namespace compositor } // namespace simgear -#endif /* SG_COMPOSITOR_COMMON_HXX */ +#endif /* SG_COMPOSITOR_UTIL_HXX */ From e227008331cbb05aa7e0d2842e74812f816361da Mon Sep 17 00:00:00 2001 From: Richard Harrison Date: Wed, 20 Feb 2019 19:00:02 +0100 Subject: [PATCH 15/33] DDS-TC handle sgio_exceptions during hash creation. ref: https://sourceforge.net/p/flightgear/codetickets/2113/ --- simgear/scene/model/ModelRegistry.cxx | 41 ++++++++++++++++----------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/simgear/scene/model/ModelRegistry.cxx b/simgear/scene/model/ModelRegistry.cxx index ca2beb1f..6b18d5a3 100644 --- a/simgear/scene/model/ModelRegistry.cxx +++ b/simgear/scene/model/ModelRegistry.cxx @@ -397,7 +397,7 @@ ModelRegistry::readImage(const string& fileName, if (iter != imageCallbackMap.end() && iter->second.valid()) return iter->second->readImage(fileName, opt); string absFileName = SGModelLib::findDataFile(fileName, opt); - + string originalFileName = absFileName; if (!fileExists(absFileName)) { SG_LOG(SG_IO, SG_ALERT, "Cannot find image file \"" << fileName << "\""); @@ -429,18 +429,26 @@ ModelRegistry::readImage(const string& fileName, // SG_LOG(SG_IO, SG_ALERT, "Hash for " + absFileName + " in cache " + hash); } else { -// SG_LOG(SG_IO, SG_ALERT, "Creating hash for " + absFileName); - hash = f.computeHash(); - filename_hash_cache.insert(absFileName, hash); - boost::optional cacheFilename = filename_hash_cache.findValue(hash); - - // possibly a shared texture - but warn the user to allow investigation. - if (cacheFilename && *cacheFilename != absFileName) { - SG_LOG(SG_IO, SG_ALERT, " Already have " + hash + " : " + *cacheFilename + " not "+absFileName); + // SG_LOG(SG_IO, SG_ALERT, "Creating hash for " + absFileName); + try { + hash = f.computeHash(); } -// SG_LOG(SG_IO, SG_ALERT, " >>>> " + hash + " :: " + newName); + catch (sg_io_exception &e) { + SG_LOG(SG_INPUT, SG_ALERT, "Modelregistry::failed to compute filehash '" << absFileName << "' " << e.getFormattedMessage()); + hash = std::string(); + } + if (hash != std::string()) { + filename_hash_cache.insert(absFileName, hash); + boost::optional cacheFilename = filename_hash_cache.findValue(hash); + + // possibly a shared texture - but warn the user to allow investigation. + if (cacheFilename && *cacheFilename != absFileName) { + SG_LOG(SG_IO, SG_ALERT, " Already have " + hash + " : " + *cacheFilename + " not " + absFileName); + } + // SG_LOG(SG_IO, SG_ALERT, " >>>> " + hash + " :: " + newName); + } + newName = cache_root + "/" + hash.substr(0, 2) + "/" + hash + ".cache.dds"; } - newName = cache_root + "/" + hash.substr(0,2) + "/" + hash + ".cache.dds"; } else { @@ -454,7 +462,7 @@ ModelRegistry::readImage(const string& fileName, // //} - if (fileExists(newName) && doRefresh) { + if (newName != std::string() && fileExists(newName) && doRefresh) { if (!filesCleaned.contains(newName)) { SG_LOG(SG_IO, SG_ALERT, "Removing previously cached effects image " + newName); SGPath(newName).remove(); @@ -463,7 +471,7 @@ ModelRegistry::readImage(const string& fileName, } - if (!fileExists(newName)) { + if (newName != std::string() && !fileExists(newName)) { res = registry->readImageImplementation(absFileName, opt); if (res.validImage()) { osg::ref_ptr srcImage = res.getImage(); @@ -637,7 +645,8 @@ SG_LOG(SG_IO, SG_WARN, pot_message << " " << absFileName); } } else { - absFileName = newName; + if (newName != std::string()) + absFileName = newName; } } } @@ -658,10 +667,8 @@ SG_LOG(SG_IO, SG_WARN, pot_message << " " << absFileName); if (srcImage1->getFileName().empty()) { srcImage1->setFileName(absFileName); } +// srcImage1->setFileName(originalFileName); - if (srcImage1->getName().empty()) { - srcImage1->setName(absFileName); - } if(cache_active && getFileExtension(absFileName) != "dds") { if (processor) { From c860b12b3d9746774bb2e3f0804a1b81fc393eb1 Mon Sep 17 00:00:00 2001 From: Richard Harrison Date: Wed, 20 Feb 2019 19:09:02 +0100 Subject: [PATCH 16/33] OSG 3.4.0 compilation fixes --- simgear/scene/material/TextureBuilder.cxx | 20 +++++++++----------- simgear/scene/model/SGReaderWriterXML.cxx | 2 +- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/simgear/scene/material/TextureBuilder.cxx b/simgear/scene/material/TextureBuilder.cxx index 38478d82..01315aa2 100644 --- a/simgear/scene/material/TextureBuilder.cxx +++ b/simgear/scene/material/TextureBuilder.cxx @@ -296,11 +296,9 @@ bool setAttrs(const TexTuple& attrs, Texture* tex, } else if (t < s && 32 <= t) { SGSceneFeatures::instance()->setTextureCompression(tex); } - tex->setMaxAnisotropy(SGSceneFeatures::instance() - ->getTextureFilter()); + tex->setMaxAnisotropy(SGSceneFeatures::instance()->getTextureFilter()); } else { - SG_LOG(SG_INPUT, SG_ALERT, "failed to load effect texture file " - << imageName); + SG_LOG(SG_INPUT, SG_ALERT, "failed to load effect texture file " << imageName); return false; } @@ -612,7 +610,7 @@ Texture* CubeMapBuilder::build(Effect* effect, Pass* pass, const SGPropertyNode* SGReaderWriterOptions* wOpts = (SGReaderWriterOptions*)options; SGReaderWriterOptions::LoadOriginHint origLOH = wOpts->getLoadOriginHint(); wOpts->setLoadOriginHint(SGReaderWriterOptions::LoadOriginHint::ORIGIN_EFFECTS); -#if OSG_VERSION_LESS_THAN(3,4,0) +#if OSG_VERSION_LESS_THAN(3,4,1) result = osgDB::readImageFile(_tuple.get<0>(), options); #else result = osgDB::readRefImageFile(_tuple.get<0>(), options); @@ -621,7 +619,7 @@ Texture* CubeMapBuilder::build(Effect* effect, Pass* pass, const SGPropertyNode* osg::Image* image = result.getImage(); cubeTexture->setImage(TextureCubeMap::POSITIVE_X, image); } -#if OSG_VERSION_LESS_THAN(3,4,0) +#if OSG_VERSION_LESS_THAN(3,4,1) result = osgDB::readImageFile(_tuple.get<1>(), options); #else result = osgDB::readRefImageFile(_tuple.get<1>(), options); @@ -630,7 +628,7 @@ Texture* CubeMapBuilder::build(Effect* effect, Pass* pass, const SGPropertyNode* osg::Image* image = result.getImage(); cubeTexture->setImage(TextureCubeMap::NEGATIVE_X, image); } -#if OSG_VERSION_LESS_THAN(3,4,0) +#if OSG_VERSION_LESS_THAN(3,4,1) result = osgDB::readImageFile(_tuple.get<2>(), options); #else result = osgDB::readRefImageFile(_tuple.get<2>(), options); @@ -639,7 +637,7 @@ Texture* CubeMapBuilder::build(Effect* effect, Pass* pass, const SGPropertyNode* osg::Image* image = result.getImage(); cubeTexture->setImage(TextureCubeMap::POSITIVE_Y, image); } -#if OSG_VERSION_LESS_THAN(3,4,0) +#if OSG_VERSION_LESS_THAN(3,4,1) result = osgDB::readImageFile(_tuple.get<3>(), options); #else result = osgDB::readRefImageFile(_tuple.get<3>(), options); @@ -648,7 +646,7 @@ Texture* CubeMapBuilder::build(Effect* effect, Pass* pass, const SGPropertyNode* osg::Image* image = result.getImage(); cubeTexture->setImage(TextureCubeMap::NEGATIVE_Y, image); } -#if OSG_VERSION_LESS_THAN(3,4,0) +#if OSG_VERSION_LESS_THAN(3,4,1) result = osgDB::readImageFile(_tuple.get<4>(), options); #else result = osgDB::readRefImageFile(_tuple.get<4>(), options); @@ -657,7 +655,7 @@ Texture* CubeMapBuilder::build(Effect* effect, Pass* pass, const SGPropertyNode* osg::Image* image = result.getImage(); cubeTexture->setImage(TextureCubeMap::POSITIVE_Z, image); } -#if OSG_VERSION_LESS_THAN(3,4,0) +#if OSG_VERSION_LESS_THAN(3,4,1) result = osgDB::readImageFile(_tuple.get<5>(), options); #else result = osgDB::readRefImageFile(_tuple.get<5>(), options); @@ -686,7 +684,7 @@ Texture* CubeMapBuilder::build(Effect* effect, Pass* pass, const SGPropertyNode* return cubeTexture.release(); osgDB::ReaderWriter::ReadResult result; -#if OSG_VERSION_LESS_THAN(3,4,0) +#if OSG_VERSION_LESS_THAN(3,4,1) result = osgDB::readImageFile(texname, options); #else result = osgDB::readRefImageFile(texname, options); diff --git a/simgear/scene/model/SGReaderWriterXML.cxx b/simgear/scene/model/SGReaderWriterXML.cxx index 71f63310..c09a9579 100644 --- a/simgear/scene/model/SGReaderWriterXML.cxx +++ b/simgear/scene/model/SGReaderWriterXML.cxx @@ -339,7 +339,7 @@ sgLoad3DModel_internal(const SGPath& path, options->setDatabasePath(texturepath.local8BitStr()); osgDB::ReaderWriter::ReadResult modelResult; -#if OSG_VERSION_LESS_THAN(3,4,0) +#if OSG_VERSION_LESS_THAN(3,4,1) modelResult = osgDB::readNodeFile(modelpath.local8BitStr(), options.get()); #else modelResult = osgDB::readRefNodeFile(modelpath.local8BitStr(), options.get()); From 052bbecc3b784365cb8c8d8872f4a1a86e58818a Mon Sep 17 00:00:00 2001 From: Richard Harrison Date: Wed, 20 Feb 2019 19:10:37 +0100 Subject: [PATCH 17/33] OSG 3.4.0 compilation fixes and anisotropy Set anisotropy on model textures after loading. --- simgear/scene/model/model.cxx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/simgear/scene/model/model.cxx b/simgear/scene/model/model.cxx index 737c7597..d449b711 100644 --- a/simgear/scene/model/model.cxx +++ b/simgear/scene/model/model.cxx @@ -41,7 +41,7 @@ SGLoadTexture2D(bool staticTexture, const std::string& path, const osgDB::Options* options, bool wrapu, bool wrapv, int) { - osg::Image* image; + osg::ref_ptr image; if (options) #if OSG_VERSION_LESS_THAN(3,4,0) image = osgDB::readImageFile(path, options); @@ -57,6 +57,8 @@ SGLoadTexture2D(bool staticTexture, const std::string& path, osg::ref_ptr texture = new osg::Texture2D; texture->setImage(image); + texture->setMaxAnisotropy(SGSceneFeatures::instance()->getTextureFilter()); + if (staticTexture) texture->setDataVariance(osg::Object::STATIC); if (wrapu) @@ -153,7 +155,7 @@ Texture2D* TextureUpdateVisitor::textureReplace(int unit, const StateAttribute* #if OSG_VERSION_LESS_THAN(3,4,0) Image* newImage = readImageFile(fullLiveryFile); #else - Image* newImage = readRefImageFile(fullLiveryFile); + osg::ref_ptr newImage = readRefImageFile(fullLiveryFile); #endif if (!newImage) return 0; @@ -163,6 +165,10 @@ Texture2D* TextureUpdateVisitor::textureReplace(int unit, const StateAttribute* return 0; } else { newTexture->setImage(newImage); + if (newImage.valid()) + { + newTexture->setMaxAnisotropy(SGSceneFeatures::instance()->getTextureFilter()); + } return newTexture; } } From 487cdc85cd084aac399a3a95fbf002c3f8c5b8d5 Mon Sep 17 00:00:00 2001 From: Richard Harrison Date: Thu, 21 Feb 2019 19:07:37 +0100 Subject: [PATCH 18/33] DDS-TC; retain original filename and fix previous commit Set the filename back to what it was after loading - seems sensible and as yet no side effects. Also the previous commit that included the handling of sgio_exceptions also broke the DDS-TC pretty much completely. --- simgear/scene/model/ModelRegistry.cxx | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/simgear/scene/model/ModelRegistry.cxx b/simgear/scene/model/ModelRegistry.cxx index 6b18d5a3..651a4ad7 100644 --- a/simgear/scene/model/ModelRegistry.cxx +++ b/simgear/scene/model/ModelRegistry.cxx @@ -426,7 +426,7 @@ ModelRegistry::readImage(const string& fileName, boost::optional cachehash = filename_hash_cache.get(absFileName); if (cachehash) { hash = *cachehash; -// SG_LOG(SG_IO, SG_ALERT, "Hash for " + absFileName + " in cache " + hash); + // SG_LOG(SG_IO, SG_ALERT, "Hash for " + absFileName + " in cache " + hash); } else { // SG_LOG(SG_IO, SG_ALERT, "Creating hash for " + absFileName); @@ -437,18 +437,18 @@ ModelRegistry::readImage(const string& fileName, SG_LOG(SG_INPUT, SG_ALERT, "Modelregistry::failed to compute filehash '" << absFileName << "' " << e.getFormattedMessage()); hash = std::string(); } - if (hash != std::string()) { - filename_hash_cache.insert(absFileName, hash); - boost::optional cacheFilename = filename_hash_cache.findValue(hash); - - // possibly a shared texture - but warn the user to allow investigation. - if (cacheFilename && *cacheFilename != absFileName) { - SG_LOG(SG_IO, SG_ALERT, " Already have " + hash + " : " + *cacheFilename + " not " + absFileName); - } - // SG_LOG(SG_IO, SG_ALERT, " >>>> " + hash + " :: " + newName); - } - newName = cache_root + "/" + hash.substr(0, 2) + "/" + hash + ".cache.dds"; } + if (hash != std::string()) { + filename_hash_cache.insert(absFileName, hash); + boost::optional cacheFilename = filename_hash_cache.findValue(hash); + + // possibly a shared texture - but warn the user to allow investigation. + if (cacheFilename && *cacheFilename != absFileName) { + SG_LOG(SG_IO, SG_ALERT, " Already have " + hash + " : " + *cacheFilename + " not " + absFileName); + } + // SG_LOG(SG_IO, SG_ALERT, " >>>> " + hash + " :: " + newName); + } + newName = cache_root + "/" + hash.substr(0, 2) + "/" + hash + ".cache.dds"; } else { @@ -667,7 +667,7 @@ SG_LOG(SG_IO, SG_WARN, pot_message << " " << absFileName); if (srcImage1->getFileName().empty()) { srcImage1->setFileName(absFileName); } -// srcImage1->setFileName(originalFileName); + srcImage1->setFileName(originalFileName); if(cache_active && getFileExtension(absFileName) != "dds") { From e2bed86bfc9278d8beffbb07bcbd3fa2c3adabe4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Garc=C3=ADa=20Li=C3=B1=C3=A1n?= Date: Sat, 23 Feb 2019 23:25:06 +0100 Subject: [PATCH 19/33] Compositor: Enable small feature and view frustum culling by default for every pass. Added option to inherit the view master camera cull mask. --- simgear/scene/viewer/Compositor.cxx | 25 ++++----- simgear/scene/viewer/Compositor.hxx | 2 - simgear/scene/viewer/CompositorPass.cxx | 74 +++++-------------------- simgear/scene/viewer/CompositorPass.hxx | 5 +- 4 files changed, 29 insertions(+), 77 deletions(-) diff --git a/simgear/scene/viewer/Compositor.cxx b/simgear/scene/viewer/Compositor.cxx index dd267504..f1caf50c 100644 --- a/simgear/scene/viewer/Compositor.cxx +++ b/simgear/scene/viewer/Compositor.cxx @@ -126,6 +126,17 @@ Compositor::update(const osg::Matrix &view_matrix, const osg::Matrix &proj_matrix) { for (auto &pass : _passes) { + if (pass->inherit_cull_mask) { + osg::Camera *camera = pass->camera; + osg::Camera *view_camera = _view->getCamera(); + camera->setCullMask(pass->cull_mask + & view_camera->getCullMask()); + camera->setCullMaskLeft(pass->cull_mask + & view_camera->getCullMaskLeft()); + camera->setCullMaskRight(pass->cull_mask + & view_camera->getCullMaskRight()); + } + if (pass->update_callback.valid()) pass->update_callback->updatePass(*pass.get(), view_matrix, proj_matrix); } @@ -189,20 +200,6 @@ Compositor::resized() } } -void -Compositor::setCameraCullMasks(osg::Node::NodeMask nm) -{ - for (const auto &pass : _passes) { - osg::Camera *camera = pass->camera; - osg::Node::NodeMask pass_cm = nm; - pass_cm &= pass->cull_mask; - - camera->setCullMask(pass_cm); - camera->setCullMaskLeft(pass_cm); - camera->setCullMaskRight(pass_cm); - } -} - bool Compositor::computeIntersection( const osg::Vec2d& windowPos, diff --git a/simgear/scene/viewer/Compositor.hxx b/simgear/scene/viewer/Compositor.hxx index 25744efb..c1761ef1 100644 --- a/simgear/scene/viewer/Compositor.hxx +++ b/simgear/scene/viewer/Compositor.hxx @@ -91,8 +91,6 @@ public: void resized(); - void setCameraCullMasks(osg::Node::NodeMask nm); - bool computeIntersection( const osg::Vec2d& windowPos, osgUtil::LineSegmentIntersector::Intersections& intersections); diff --git a/simgear/scene/viewer/CompositorPass.cxx b/simgear/scene/viewer/CompositorPass.cxx index 96f6e9e0..8ee399d7 100644 --- a/simgear/scene/viewer/CompositorPass.cxx +++ b/simgear/scene/viewer/CompositorPass.cxx @@ -68,9 +68,6 @@ PassBuilder::build(Compositor *compositor, const SGPropertyNode *root) if (!eff_override_file.empty()) pass->effect_override = makeEffect(eff_override_file, true, 0); - pass->cull_mask = std::stoul(root->getStringValue("cull-mask", "0xffffff"), - nullptr, 0); - osg::Camera *camera = new Camera; pass->camera = camera; @@ -83,6 +80,17 @@ PassBuilder::build(Compositor *compositor, const SGPropertyNode *root) camera->setProjectionResizePolicy(osg::Camera::FIXED); camera->setComputeNearFarMode(osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR); + // XXX: Should we make this configurable? + camera->setCullingMode(CullSettings::SMALL_FEATURE_CULLING + | CullSettings::VIEW_FRUSTUM_CULLING); + + osg::Node::NodeMask cull_mask = + std::stoul(root->getStringValue("cull-mask", "0xffffffff"), nullptr, 0); + pass->cull_mask = cull_mask; + camera->setCullMask(pass->cull_mask); + camera->setCullMaskLeft(pass->cull_mask); + camera->setCullMaskRight(pass->cull_mask); + osg::Vec4f clear_color(0.0f, 0.0f, 0.0f, 0.0f); const SGPropertyNode *p_clear_color = root->getChild("clear-color"); if (p_clear_color) @@ -265,7 +273,6 @@ struct QuadPassBuilder : public PassBuilder { public: virtual Pass *build(Compositor *compositor, const SGPropertyNode *root) { osg::ref_ptr pass = PassBuilder::build(compositor, root); - pass->useMastersSceneData = false; osg::Camera *camera = pass->camera; camera->setAllowEventFocus(false); @@ -367,62 +374,6 @@ RegisterPassBuilder registerQuadPass("quad"); //------------------------------------------------------------------------------ -#if 0 -class ShadowMapUpdateCallback; // Forward declaration - -class ShadowMapCullCallback : public osg::NodeCallback { -public: - ShadowMapCullCallback(int light_num, float near_m, float far_m) : - _light_num(light_num), - _near_m(near_m), - _far_m(far_m) {} - - virtual void operator()(osg::Node *node, osg::NodeVisitor *nv) { - osg::Camera *light_camera = static_cast(node); - osgUtil::CullVisitor *cv = dynamic_cast(nv); - - osg::Vec4 light_pos = _light->getPosition(); - if (light_pos.w() != 0.0) { - // We only support directional light sources for now - traverse(node, nv); - return; - } - - osg::Vec3 light_dir = - osg::Vec3(light_pos.x(), light_pos.y(), light_pos.z()); - - // Find the light - // Mostly taken from osgShadow - osgUtil::RenderStage *rs = cv->getRenderStage(); - osgUtil::PositionalStateContainer::AttrMatrixList &aml = - rs->getPositionalStateContainer()->getAttrMatrixList(); - for (auto &itr : aml) { - const osg::Light *light = dynamic_cast(itr.first.get()); - if (light) { - if (light->getLightNum() != _light_num) - continue; - - osg::Vec4 light_pos = light->getPosition(); - if (light_pos.w() == 0.0) { - // We only want directional lights - light_dir = osg::Vec3(light_pos.x(), light_pos.y(), light_pos.z()); - } - } - } - traverse(node, nv); - } -protected: - friend ShadowMapUpdateCallback; - int _light_num; - float _near_m; - float _far_m; - osg::Matrix _camera_view; - osg::Matrix _camera_proj; - osg::observer_ptr _light; - osg::ref_ptr _light_matrix_uniform; -}; -#endif - class LightFinder : public osg::NodeVisitor { public: LightFinder(const std::string &name) : @@ -565,6 +516,7 @@ protected: struct ShadowMapPassBuilder : public PassBuilder { virtual Pass *build(Compositor *compositor, const SGPropertyNode *root) { osg::ref_ptr pass = PassBuilder::build(compositor, root); + pass->useMastersSceneData = true; osg::Camera *camera = pass->camera; camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF_INHERIT_VIEWPOINT); @@ -676,6 +628,8 @@ struct ScenePassBuilder : public PassBuilder { public: virtual Pass *build(Compositor *compositor, const SGPropertyNode *root) { osg::ref_ptr pass = PassBuilder::build(compositor, root); + pass->useMastersSceneData = true; + pass->inherit_cull_mask = true; osg::Camera *camera = pass->camera; camera->setAllowEventFocus(true); diff --git a/simgear/scene/viewer/CompositorPass.hxx b/simgear/scene/viewer/CompositorPass.hxx index fae90d29..68fe89e3 100644 --- a/simgear/scene/viewer/CompositorPass.hxx +++ b/simgear/scene/viewer/CompositorPass.hxx @@ -45,8 +45,9 @@ class Compositor; */ struct Pass : public osg::Referenced { Pass() : - useMastersSceneData(true), + useMastersSceneData(false), cull_mask(0xffffff), + inherit_cull_mask(false), viewport_width_scale(0.0f), viewport_height_scale(0.0f) {} @@ -57,6 +58,8 @@ struct Pass : public osg::Referenced { osg::ref_ptr effect_override; bool useMastersSceneData; osg::Node::NodeMask cull_mask; + /** Whether the cull mask is ANDed with the view master camera cull mask. */ + bool inherit_cull_mask; float viewport_width_scale; float viewport_height_scale; From 10a701308dd5a62d8723d5dd1ff60a76d8b36fd6 Mon Sep 17 00:00:00 2001 From: James Turner Date: Tue, 19 Feb 2019 17:59:31 +0000 Subject: [PATCH 20/33] Make star cutoff controllable at runtime. When a new property /environment/star-magnitude-cutoff is defined, use this to set an upper bound on the magnitude of stars displayed. --- simgear/scene/sky/sky.cxx | 2 +- simgear/scene/sky/stars.cxx | 25 ++++++++++++++++++------- simgear/scene/sky/stars.hxx | 8 +++++--- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/simgear/scene/sky/sky.cxx b/simgear/scene/sky/sky.cxx index 389d8bf4..25df35d7 100644 --- a/simgear/scene/sky/sky.cxx +++ b/simgear/scene/sky/sky.cxx @@ -94,7 +94,7 @@ void SGSky::build( double h_radius_m, planets = new SGStars; _ephTransform->addChild( planets->build(eph.getNumPlanets(), eph.getPlanets(), h_radius_m) ); - stars = new SGStars; + stars = new SGStars(property_tree_node); _ephTransform->addChild( stars->build(eph.getNumStars(), eph.getStars(), h_radius_m) ); moon = new SGMoon; diff --git a/simgear/scene/sky/stars.cxx b/simgear/scene/sky/stars.cxx index d4611e01..6eb70fa1 100644 --- a/simgear/scene/sky/stars.cxx +++ b/simgear/scene/sky/stars.cxx @@ -30,9 +30,11 @@ #include #include #include +#include #include #include +#include #include #include @@ -47,9 +49,14 @@ #include "stars.hxx" // Constructor -SGStars::SGStars( void ) : -old_phase(-1) +SGStars::SGStars( SGPropertyNode* props ) : + old_phase(-1) { + if (props) { + // don't create here - if it's not defined, we won't use the cutoff + // from a property + _cutoffProperty = props->getNode("star-magnitude-cutoff"); + } } @@ -161,10 +168,17 @@ bool SGStars::repaint( double sun_angle, int num, const SGVec3d star_data[] ) { cutoff = 0.0; phase = 7; } - - if( phase != old_phase ) { + + if (_cutoffProperty) { + double propCutoff = _cutoffProperty->getDoubleValue(); + cutoff = std::min(propCutoff, cutoff); + } + + if ((phase != old_phase) || (cutoff != _cachedCutoff)) { // cout << " phase change, repainting stars, num = " << num << endl; old_phase = phase; + _cachedCutoff = cutoff; + for ( int i = 0; i < num; ++i ) { // if ( star_data[i][2] < min ) { min = star_data[i][2]; } // if ( star_data[i][2] > max ) { max = star_data[i][2]; } @@ -190,11 +204,8 @@ bool SGStars::repaint( double sun_angle, int num, const SGVec3d star_data[] ) { if (alpha < 0.0) { alpha = 0.0; } (*cl)[i] = osg::Vec4(1, 1, 1, alpha); - // cout << "alpha[" << i << "] = " << alpha << endl; } cl->dirty(); - } else { - // cout << " no phase change, skipping" << endl; } // cout << "min = " << min << " max = " << max << " count = " << num diff --git a/simgear/scene/sky/stars.hxx b/simgear/scene/sky/stars.hxx index bf311e31..10b0140c 100644 --- a/simgear/scene/sky/stars.hxx +++ b/simgear/scene/sky/stars.hxx @@ -33,18 +33,20 @@ #include #include - +#include class SGStars : public SGReferenced { osg::ref_ptr cl; int old_phase; // data for optimization - + + double _cachedCutoff = 0.0; + SGPropertyNode_ptr _cutoffProperty; public: // Constructor - SGStars( void ); + SGStars( SGPropertyNode* props = nullptr); // Destructor ~SGStars( void ); From 7e969f4e2873ca08eb7c152628187c180b7d8caf Mon Sep 17 00:00:00 2001 From: James Turner Date: Mon, 25 Feb 2019 16:12:30 +0000 Subject: [PATCH 21/33] Updated star rendering phases/parameters As part of this, adjust the normalisation so the active cutoff value corresponds to 0.0 normalised intensity. --- simgear/scene/sky/stars.cxx | 66 ++++++++++++++++++++++++++----------- 1 file changed, 46 insertions(+), 20 deletions(-) diff --git a/simgear/scene/sky/stars.cxx b/simgear/scene/sky/stars.cxx index 6eb70fa1..a079cdee 100644 --- a/simgear/scene/sky/stars.cxx +++ b/simgear/scene/sky/stars.cxx @@ -124,49 +124,77 @@ SGStars::build( int num, const SGVec3d star_data[], double star_dist ) { // 0 degrees = high noon // 90 degrees = sun rise/set // 180 degrees = darkest midnight -bool SGStars::repaint( double sun_angle, int num, const SGVec3d star_data[] ) { - // cout << "repainting stars" << endl; - // double min = 100; - // double max = -100; + +bool SGStars::repaint( double sun_angle, int num, const SGVec3d star_data[] ) +{ double mag, nmag, alpha, factor, cutoff; + /* + maximal magnitudes under dark sky on Earth, from Eq.(90) and (91) of astro-ph/1405.4209 + For (18 < musky < 20) + mmax = 0.27 musky + 0.8 - 2.5 * log(F) + + For (19.5 µsky 22) + mmax = 0.383 musky - 1.44 - 2.5 * log(F) + + + Let's take F = 1.4 for healthy young pilot + mudarksky ~ 22 mag/arcsec^2 => mmax=6.2 + muastrotwilight ~ 20 mag/arsec^2 => mmax=5.4 + mu99deg ~ 17.5 mag/arcsec^2 => mmax=4.7 + mu97.5deg ~ 16 mag/arcsec^2 => ? let's keep it rough + */ + + double mag_nakedeye = 6.2; + double mag_twilight_astro = 5.4; + double mag_twilight_nautic = 4.7; + + // sirius, brightest star (not brightest object) + double mag_min = -1.46; + int phase; // determine which star structure to draw - if ( sun_angle > (SGD_PI_2 + 10.0 * SGD_DEGREES_TO_RADIANS ) ) { - // deep night + if ( sun_angle > (SGD_PI_2 + 18.0 * SGD_DEGREES_TO_RADIANS ) ) { + // deep night, atmosphere is not lighten by the sun factor = 1.0; - cutoff = 4.5; + cutoff = mag_nakedeye; phase = 0; - } else if ( sun_angle > (SGD_PI_2 + 8.8 * SGD_DEGREES_TO_RADIANS ) ) { + } else if ( sun_angle > (SGD_PI_2 + 12.0 * SGD_DEGREES_TO_RADIANS ) ) { + // less than 18deg and more than 12deg is astronomical twilight factor = 1.0; - cutoff = 3.8; + cutoff = mag_twilight_astro; phase = 1; + } else if ( sun_angle > (SGD_PI_2 + 9.0 * SGD_DEGREES_TO_RADIANS ) ) { + // less 12deg and more than 6deg is is nautical twilight + factor = 1.0; + cutoff = mag_twilight_nautic; + phase = 2; } else if ( sun_angle > (SGD_PI_2 + 7.5 * SGD_DEGREES_TO_RADIANS ) ) { factor = 0.95; cutoff = 3.1; - phase = 2; + phase = 3; } else if ( sun_angle > (SGD_PI_2 + 7.0 * SGD_DEGREES_TO_RADIANS ) ) { factor = 0.9; cutoff = 2.4; - phase = 3; + phase = 4; } else if ( sun_angle > (SGD_PI_2 + 6.5 * SGD_DEGREES_TO_RADIANS ) ) { factor = 0.85; cutoff = 1.8; - phase = 4; + phase = 5; } else if ( sun_angle > (SGD_PI_2 + 6.0 * SGD_DEGREES_TO_RADIANS ) ) { factor = 0.8; cutoff = 1.2; - phase = 5; + phase = 6; } else if ( sun_angle > (SGD_PI_2 + 5.5 * SGD_DEGREES_TO_RADIANS ) ) { factor = 0.75; cutoff = 0.6; - phase = 6; + phase = 7; } else { // early dusk or late dawn factor = 0.7; cutoff = 0.0; - phase = 7; + phase = 8; } if (_cutoffProperty) { @@ -183,17 +211,15 @@ bool SGStars::repaint( double sun_angle, int num, const SGVec3d star_data[] ) { // if ( star_data[i][2] < min ) { min = star_data[i][2]; } // if ( star_data[i][2] > max ) { max = star_data[i][2]; } - // magnitude ranges from -1 (bright) to 4 (dim). The + // magnitude ranges from -1 (bright) to 6 (dim). The // range of star and planet magnitudes can actually go // outside of this, but for our purpose, if it is brighter - // that -1, we'll color it full white/alpha anyway and 4 - // is a convenient cutoff point which keeps the number of - // stars drawn at about 500. + // that magmin, we'll color it full white/alpha anyway // color (magnitude) mag = star_data[i][2]; if ( mag < cutoff ) { - nmag = ( 4.5 - mag ) / 5.5; // translate to 0 ... 1.0 scale + nmag = ( cutoff - mag ) / (cutoff - mag_min); // translate to 0 ... 1.0 scale alpha = nmag * 0.85 + 0.15; // translate to a 0.15 ... 1.0 scale alpha *= factor; // dim when the sun is brighter } else { From 846f024e91252657313585455ac8dbfc5dbe9657 Mon Sep 17 00:00:00 2001 From: Richard Harrison Date: Fri, 1 Mar 2019 19:00:43 +0100 Subject: [PATCH 22/33] SGSubsytem name compatibility for other compilers; changed to use const char* rather than const std::string & as this doesn't work on GCC; and besides std::string converts nicely to const char* and most of the times this is used it is actually a string literal. --- simgear/structure/subsystem_mgr.cxx | 4 ++-- simgear/structure/subsystem_mgr.hxx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/simgear/structure/subsystem_mgr.cxx b/simgear/structure/subsystem_mgr.cxx index 799457f4..bd990546 100644 --- a/simgear/structure/subsystem_mgr.cxx +++ b/simgear/structure/subsystem_mgr.cxx @@ -232,7 +232,7 @@ public: -SGSubsystemGroup::SGSubsystemGroup(const std::string &name) : +SGSubsystemGroup::SGSubsystemGroup(const char *name) : _fixedUpdateTime(-1.0), _updateTimeRemainder(0.0), _initPosition(-1) @@ -837,7 +837,7 @@ namespace { } // end of anonymous namespace -SGSubsystemMgr::SGSubsystemMgr (const std::string &name) : +SGSubsystemMgr::SGSubsystemMgr (const char *name) : _groups(MAX_GROUPS) { if (global_defaultSubsystemManager == nullptr) { diff --git a/simgear/structure/subsystem_mgr.hxx b/simgear/structure/subsystem_mgr.hxx index 3a5c4013..b991a9ef 100644 --- a/simgear/structure/subsystem_mgr.hxx +++ b/simgear/structure/subsystem_mgr.hxx @@ -378,7 +378,7 @@ typedef SGSharedPtr SGSubsystemRef; class SGSubsystemGroup : public SGSubsystem { public: - SGSubsystemGroup (const std::string &name); + SGSubsystemGroup (const char *name); virtual ~SGSubsystemGroup (); void init() override; @@ -501,7 +501,7 @@ public: MAX_GROUPS }; - SGSubsystemMgr (const std::string &name); + SGSubsystemMgr (const char *name); virtual ~SGSubsystemMgr (); void init () override; From fdcc9c9e0e09e09c7c9812bc2c953cf2048d5706 Mon Sep 17 00:00:00 2001 From: Scott Giese Date: Tue, 5 Mar 2019 23:56:47 -0600 Subject: [PATCH 23/33] Fix compile issue with OSG versions below 3.4 --- simgear/scene/model/model.cxx | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/simgear/scene/model/model.cxx b/simgear/scene/model/model.cxx index d449b711..3eea102f 100644 --- a/simgear/scene/model/model.cxx +++ b/simgear/scene/model/model.cxx @@ -139,19 +139,21 @@ Texture2D* TextureUpdateVisitor::textureReplace(int unit, const StateAttribute* if (image) { // The currently loaded file name fullFilePath = &image->getFileName(); - } else { fullFilePath = &texture->getName(); } + // The short name string fileName = getSimpleFileName(*fullFilePath); if (fileName.empty()) return 0; + // The name that should be found with the current database path string fullLiveryFile = findFileInPath(fileName, _pathList); // If it is empty or they are identical then there is nothing to do if (fullLiveryFile.empty() || fullLiveryFile == *fullFilePath) return 0; + #if OSG_VERSION_LESS_THAN(3,4,0) Image* newImage = readImageFile(fullLiveryFile); #else @@ -159,18 +161,23 @@ Texture2D* TextureUpdateVisitor::textureReplace(int unit, const StateAttribute* #endif if (!newImage) return 0; + CopyOp copyOp(CopyOp::DEEP_COPY_ALL & ~CopyOp::DEEP_COPY_IMAGES); Texture2D* newTexture = static_cast(copyOp(texture)); - if (!newTexture) { + if (!newTexture) return 0; - } else { - newTexture->setImage(newImage); - if (newImage.valid()) - { - newTexture->setMaxAnisotropy(SGSceneFeatures::instance()->getTextureFilter()); - } - return newTexture; + + newTexture->setImage(newImage); +#if OSG_VERSION_LESS_THAN(3,4,0) + if (newImage->valid()) +#else + if (newImage.valid()) +#endif + { + newTexture->setMaxAnisotropy(SGSceneFeatures::instance()->getTextureFilter()); } + + return newTexture; } StateSet* TextureUpdateVisitor::cloneStateSet(const StateSet* stateSet) From 7e0a10c016d0f8deeb77e5329a7d8b44161e7762 Mon Sep 17 00:00:00 2001 From: Stuart Buchanan Date: Tue, 12 Mar 2019 21:45:34 +0000 Subject: [PATCH 24/33] Set radius for PagedLOD Speculative fix to address scenery chunks being unloaded unexpectedly under high load. --- simgear/scene/tgdb/ReaderWriterSTG.cxx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/simgear/scene/tgdb/ReaderWriterSTG.cxx b/simgear/scene/tgdb/ReaderWriterSTG.cxx index 4a3887a4..100fa821 100644 --- a/simgear/scene/tgdb/ReaderWriterSTG.cxx +++ b/simgear/scene/tgdb/ReaderWriterSTG.cxx @@ -620,7 +620,8 @@ struct ReaderWriterSTG::_ModelBin { pagedLOD->setFileName(pagedLOD->getNumChildren(), "Dummy name - use the stored data in the read file callback"); // Objects may end up displayed up to 2x the object range. - pagedLOD->setRange(pagedLOD->getNumChildren(), 0, 2.0 * _object_range_rough + SG_TILE_RADIUS); + pagedLOD->setRange(pagedLOD->getNumChildren(), 0, 2.0 * _object_range_rough); + pagedLOD->setRadius(SG_TILE_RADIUS); SG_LOG( SG_TERRAIN, SG_DEBUG, "Tile PagedLOD Center: " << pagedLOD->getCenter().x() << "," << pagedLOD->getCenter().y() << "," << pagedLOD->getCenter().z() ); SG_LOG( SG_TERRAIN, SG_DEBUG, "Tile PagedLOD Range: " << (2.0 * _object_range_rough + SG_TILE_RADIUS)); return pagedLOD; From a0bdcb30fd2622285a59fbc79946ddc07523dea8 Mon Sep 17 00:00:00 2001 From: Stuart Buchanan Date: Tue, 12 Mar 2019 21:53:22 +0000 Subject: [PATCH 25/33] Correct log messages. --- simgear/scene/tgdb/ReaderWriterSTG.cxx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/simgear/scene/tgdb/ReaderWriterSTG.cxx b/simgear/scene/tgdb/ReaderWriterSTG.cxx index 100fa821..a417df45 100644 --- a/simgear/scene/tgdb/ReaderWriterSTG.cxx +++ b/simgear/scene/tgdb/ReaderWriterSTG.cxx @@ -623,7 +623,8 @@ struct ReaderWriterSTG::_ModelBin { pagedLOD->setRange(pagedLOD->getNumChildren(), 0, 2.0 * _object_range_rough); pagedLOD->setRadius(SG_TILE_RADIUS); SG_LOG( SG_TERRAIN, SG_DEBUG, "Tile PagedLOD Center: " << pagedLOD->getCenter().x() << "," << pagedLOD->getCenter().y() << "," << pagedLOD->getCenter().z() ); - SG_LOG( SG_TERRAIN, SG_DEBUG, "Tile PagedLOD Range: " << (2.0 * _object_range_rough + SG_TILE_RADIUS)); + SG_LOG( SG_TERRAIN, SG_DEBUG, "Tile PagedLOD Range: " << (2.0 * _object_range_rough)); + SG_LOG( SG_TERRAIN, SG_DEBUG, "Tile PagedLOD Radius: " << SG_TILE_RADIUS); return pagedLOD; } } From 4352c0a7371361d90affcc6da4bcccfc66373dd6 Mon Sep 17 00:00:00 2001 From: Torsten Dreyer Date: Wed, 13 Mar 2019 21:20:49 +0100 Subject: [PATCH 26/33] new version: 2019.1.1 --- version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version b/version index 5c8cec8e..2f6f9450 100644 --- a/version +++ b/version @@ -1 +1 @@ -2018.4.0 +2019.1.1 From f96437402732f979f5921e2fde4fa97fbe4e5cd7 Mon Sep 17 00:00:00 2001 From: Torsten Dreyer Date: Wed, 13 Mar 2019 21:20:49 +0100 Subject: [PATCH 27/33] new version: 2019.2.0 --- version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version b/version index 2f6f9450..b13705b7 100644 --- a/version +++ b/version @@ -1 +1 @@ -2019.1.1 +2019.2.0 From 07929625913f8e43f1537da135d599e5f81dfbf1 Mon Sep 17 00:00:00 2001 From: Richard Harrison Date: Sun, 17 Mar 2019 18:20:27 +0100 Subject: [PATCH 28/33] add simgear::strutils::replace --- simgear/misc/strutils.cxx | 10 ++++++++++ simgear/misc/strutils.hxx | 13 ++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/simgear/misc/strutils.cxx b/simgear/misc/strutils.cxx index 22f0d810..6d2cbeaa 100644 --- a/simgear/misc/strutils.cxx +++ b/simgear/misc/strutils.cxx @@ -1007,6 +1007,16 @@ std::string unescape(const char* s) } return r; } +std::string replace(std::string source, const std::string search, const std::string replacement, std::size_t start_pos) +{ + if (start_pos < source.length()) { + while ((start_pos = source.find(search, start_pos)) != std::string::npos) { + source.replace(start_pos, search.length(), replacement); + start_pos += replacement.length(); + } + } + return source; +} string sanitizePrintfFormat(const string& input) { diff --git a/simgear/misc/strutils.hxx b/simgear/misc/strutils.hxx index d8fe9fbb..a289b657 100644 --- a/simgear/misc/strutils.hxx +++ b/simgear/misc/strutils.hxx @@ -333,7 +333,18 @@ namespace simgear { inline std::string unescape(const std::string& str) { return unescape(str.c_str()); } - /** + /** + * Replace matching elements of string. + * + * @param source source string + * @param search search string + * @param replace replacement string + * @param start_pos starting position for replacement in source. Checked to ensure less than length of source. + * @return string with all occurrences of search changed to replace + */ + std::string replace(std::string source, const std::string search, const std::string replacement, std::size_t start_pos = 0); + + /** * Check a printf-style format string for dangerous (buffer-overflowing, * memory re-writing) format tokens. If a problematic token is * found, logs an error (SG_WARN) and returns an empty format string. From 5fdf6ef4f3cb229ffdba1a68b745586c9308e6be Mon Sep 17 00:00:00 2001 From: James Turner Date: Sun, 24 Mar 2019 14:13:42 +0000 Subject: [PATCH 29/33] Simgear parts of queued command execution Extend the command API to allow posting commands from arbitrary threads, for deferred execution on the main thread. Additional pieces on the FG side to follow shortly. --- simgear/structure/CMakeLists.txt | 6 +- simgear/structure/commands.cxx | 115 ++++++++++++++++++++----- simgear/structure/commands.hxx | 53 ++++++++---- simgear/structure/test_commands.cxx | 128 ++++++++++++++++++++++++++++ 4 files changed, 266 insertions(+), 36 deletions(-) create mode 100644 simgear/structure/test_commands.cxx diff --git a/simgear/structure/CMakeLists.txt b/simgear/structure/CMakeLists.txt index 5d81ad67..fb13114d 100644 --- a/simgear/structure/CMakeLists.txt +++ b/simgear/structure/CMakeLists.txt @@ -39,7 +39,7 @@ set(SOURCES commands.cxx event_mgr.cxx exception.cxx - subsystem_mgr.cxx + subsystem_mgr.cxx StateMachine.cxx ) @@ -63,6 +63,10 @@ add_executable(test_shared_ptr shared_ptr_test.cpp) target_link_libraries(test_shared_ptr ${TEST_LIBS}) add_test(shared_ptr ${EXECUTABLE_OUTPUT_PATH}/test_shared_ptr) +add_executable(test_commands test_commands.cxx) +target_link_libraries(test_commands ${TEST_LIBS}) +add_test(subsystems ${EXECUTABLE_OUTPUT_PATH}/test_commands) + endif(ENABLE_TESTS) add_boost_test(function_list diff --git a/simgear/structure/commands.cxx b/simgear/structure/commands.cxx index 150e16aa..5621c8d8 100644 --- a/simgear/structure/commands.cxx +++ b/simgear/structure/commands.cxx @@ -4,9 +4,7 @@ // // $Id$ -#ifdef HAVE_CONFIG_H -# include -#endif +#include #include #include @@ -20,23 +18,46 @@ #include #include +struct Invocation +{ + std::string command; + SGPropertyNode_ptr args; +}; + +class SGCommandMgr::Private +{ +public: + SGPropertyNode_ptr _rootNode; + + using InvocactionVec = std::vector; + InvocactionVec _queue; + + SGMutex _mutex; + + using command_map = std::map ; + command_map _commands; + + long _mainThreadId = 0; +}; //////////////////////////////////////////////////////////////////////// // Implementation of SGCommandMgr class. //////////////////////////////////////////////////////////////////////// -static SGCommandMgr* static_instance = NULL; +static SGCommandMgr* static_instance = nullptr; -SGCommandMgr::SGCommandMgr () +SGCommandMgr::SGCommandMgr () : + d(new Private) { - assert(static_instance == NULL); + d->_mainThreadId = SGThread::current(); + assert(static_instance == nullptr); static_instance = this; } SGCommandMgr::~SGCommandMgr () { assert(static_instance == this); - static_instance = NULL; + static_instance = nullptr; } SGCommandMgr* @@ -45,28 +66,37 @@ SGCommandMgr::instance() return static_instance ? static_instance : new SGCommandMgr; } +void SGCommandMgr::setImplicitRoot(SGPropertyNode *root) +{ + assert(root); + d->_rootNode = root; +} + void SGCommandMgr::addCommandObject (const std::string &name, Command* command) { - if (_commands.find(name) != _commands.end()) - throw sg_exception("duplicate command name:" + name); - - _commands[name] = command; +#if !defined(NDEBUG) + assert(SGThread::current() == d->_mainThreadId); +#endif + if (d->_commands.find(name) != d->_commands.end()) + throw sg_exception("duplicate command name:" + name); + + d->_commands[name] = command; } SGCommandMgr::Command* SGCommandMgr::getCommand (const std::string &name) const { - const command_map::const_iterator it = _commands.find(name); - return (it != _commands.end() ? it->second : 0); + const auto it = d->_commands.find(name); + return (it != d->_commands.end() ? it->second : nullptr); } string_list SGCommandMgr::getCommandNames () const { string_list names; - command_map::const_iterator it = _commands.begin(); - command_map::const_iterator last = _commands.end(); + auto it = d->_commands.begin(); + auto last = d->_commands.end(); while (it != last) { names.push_back(it->first); ++it; @@ -77,13 +107,24 @@ SGCommandMgr::getCommandNames () const bool SGCommandMgr::execute (const std::string &name, const SGPropertyNode * arg, SGPropertyNode *root) const { +#if !defined(NDEBUG) + if (SGThread::current() != d->_mainThreadId) { + SG_LOG(SG_GENERAL, SG_WARN, "calling SGCommandMgr::execute from a different thread than expected. Command is:" << name); + } + // assert(SGThread::current() == d->_mainThreadId); +#endif Command* command = getCommand(name); - if (command == 0) + if (command == nullptr) { SG_LOG(SG_GENERAL, SG_WARN, "command not found: '" << name << "'"); return false; } + // use implicit root node if caller did not define one explicitly + if (root == nullptr) { + root = d->_rootNode.get(); + } + try { return (*command)(arg, root); @@ -122,13 +163,47 @@ SGCommandMgr::execute (const std::string &name, const SGPropertyNode * arg, SGPr bool SGCommandMgr::removeCommand(const std::string& name) { - command_map::iterator it = _commands.find(name); - if (it == _commands.end()) +#if !defined(NDEBUG) + assert(SGThread::current() == d->_mainThreadId); +#endif + auto it = d->_commands.find(name); + if (it == d->_commands.end()) return false; - + delete it->second; - _commands.erase(it); + d->_commands.erase(it); return true; } +void SGCommandMgr::queuedExecute(const std::string &name, const SGPropertyNode* arg) +{ + Invocation invoke = {name, new SGPropertyNode}; + copyProperties(arg, invoke.args); + SGGuard g(d->_mutex); + d->_queue.push_back(invoke); +} + +void SGCommandMgr::executedQueuedCommands() +{ +#if !defined(NDEBUG) + assert(SGThread::current() == d->_mainThreadId); +#endif + + // locked swap with the shared queue + Private::InvocactionVec q; + { + SGGuard g(d->_mutex); + d->_queue.swap(q); + } + + for (auto i : q) { + bool ok = execute(i.command, i.args); + if (!ok) { + SG_LOG(SG_GENERAL, SG_WARN, "queued execute of command " << i.command + << "failed"); + } + } +} + + // end of commands.cxx diff --git a/simgear/structure/commands.hxx b/simgear/structure/commands.hxx index 58d8c281..e8bf4e96 100644 --- a/simgear/structure/commands.hxx +++ b/simgear/structure/commands.hxx @@ -15,12 +15,13 @@ #include #include +#include #include // forward decls class SGPropertyNode; - + /** * Manage commands. * @@ -44,7 +45,7 @@ public: virtual bool operator()(const SGPropertyNode * arg, SGPropertyNode *root) = 0; }; - + typedef bool (*command_t) (const SGPropertyNode * arg, SGPropertyNode *root); private: @@ -74,7 +75,7 @@ private: ObjPtr pObj_; MemFn pMemFn_; }; - + /** * Helper template functions. */ @@ -84,7 +85,7 @@ private: { return new MethodCommand(pObj, pMemFn ); } - + public: /** * Default constructor (sets instance to created item) @@ -98,6 +99,12 @@ public: static SGCommandMgr* instance(); + /** + * specify the root node to use if one is not explicitly provided when + * executing a command. + */ + void setImplicitRoot(SGPropertyNode* root); + /** * Register a new command with the manager. * @@ -109,15 +116,15 @@ public: */ void addCommand(const std::string& name, command_t f) { addCommandObject(name, new FunctionCommand(f)); } - + void addCommandObject (const std::string &name, Command* command); template void addCommand(const std::string& name, const OBJ& o, METHOD m) - { + { addCommandObject(name, make_functor(o,m)); } - + /** * Look up an existing command. * @@ -147,21 +154,37 @@ public: * @return true if the command is present and executes successfully, * false otherwise. */ - virtual bool execute (const std::string &name, const SGPropertyNode * arg, SGPropertyNode *root) const; + virtual bool execute (const std::string &name, const SGPropertyNode * arg, SGPropertyNode *root = nullptr) const; + + /** + * Queue a command for execution on the main thread / thread owning + * this command manager. (In practice in FlightGear, this means the same thing) + * The argument node will be copied immediately, so changes made after this call + * will not be reflected when the command is executed. + * + * Note there is no way to unqueue a command, or find out when the execution + * actually occurs, at present. Queued ommands are executed in the order submitted, + * however, which can be used to infer completion. + * + * Queued execution always uses the implicit root node. + */ + void queuedExecute(const std::string &name, const SGPropertyNode* arg); + + /** + * Dispatch queued command. FlightGear calls this once per frame + * on the main thread. + * + */ + void executedQueuedCommands(); /** * Remove a command registration */ bool removeCommand(const std::string& name); -protected: - - private: - - typedef std::map command_map; - command_map _commands; - + class Private; + std::unique_ptr d; }; #endif // __COMMANDS_HXX diff --git a/simgear/structure/test_commands.cxx b/simgear/structure/test_commands.cxx new file mode 100644 index 00000000..a107e8b0 --- /dev/null +++ b/simgear/structure/test_commands.cxx @@ -0,0 +1,128 @@ +#include + +#include +#include + +#include +#include +#include +#include +#include + +using std::string; +using std::cout; +using std::cerr; +using std::endl; + +SGPropertyNode_ptr test_rootNode; + +bool commandAFunc(const SGPropertyNode* args, SGPropertyNode* root) +{ + SG_VERIFY(root == test_rootNode.get()); + root->setIntValue("propA", args->getIntValue("argA")); + return true; +} + +bool commandBFunc(const SGPropertyNode* args, SGPropertyNode* root) +{ + SG_VERIFY(root == test_rootNode.get()); + root->setIntValue("propB", args->getIntValue("argB")); + return args->getBoolValue("result"); +} + +void testBasicCommands() +{ + test_rootNode.reset(new SGPropertyNode); + SGCommandMgr::instance()->setImplicitRoot(test_rootNode.get()); + + SGCommandMgr::instance()->addCommand("cmd-a", commandAFunc); + SGCommandMgr::instance()->addCommand("cmd-b", commandBFunc); + + auto names = SGCommandMgr::instance()->getCommandNames(); + SG_CHECK_EQUAL(names.size(), 2); + SG_VERIFY(std::count(names.begin(), names.end(), "cmd-a")); + SG_VERIFY(std::count(names.begin(), names.end(), "cmd-b")); + + { + SGPropertyNode_ptr args(new SGPropertyNode); + args->setIntValue("argA", 42); + bool ok = SGCommandMgr::instance()->execute("cmd-a", args); + SG_VERIFY(ok); + SG_CHECK_EQUAL(test_rootNode->getIntValue("propA"), 42); + } + + { + SGPropertyNode_ptr args(new SGPropertyNode); + args->setIntValue("argB", 99); + args->setBoolValue("result", true); + bool ok = SGCommandMgr::instance()->execute("cmd-b", args); + SG_VERIFY(ok); + SG_CHECK_EQUAL(test_rootNode->getIntValue("propB"), 99); + } + + SGCommandMgr::instance()->removeCommand("cmd-a"); + names = SGCommandMgr::instance()->getCommandNames(); + SG_CHECK_EQUAL(names.size(), 1); + SG_VERIFY(std::count(names.begin(), names.end(), "cmd-b")); + + // check we can't execute a removed command + { + SGPropertyNode_ptr args(new SGPropertyNode); + args->setIntValue("argA", 66); + bool ok = SGCommandMgr::instance()->execute("cmd-a", args); + SG_VERIFY(!ok); + SG_CHECK_EQUAL(test_rootNode->getIntValue("propA"), 42); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +void testQueuedExec() +{ + // delete the previous instance, so next call re-creates + delete SGCommandMgr::instance(); + + test_rootNode.reset(new SGPropertyNode); + test_rootNode->setIntValue("propA", 71); + + SGCommandMgr::instance()->setImplicitRoot(test_rootNode.get()); + + SGCommandMgr::instance()->addCommand("cmd-a", commandAFunc); + SGCommandMgr::instance()->addCommand("cmd-b", commandBFunc); + + { + SGPropertyNode_ptr args(new SGPropertyNode); + args->setIntValue("argA", 99); + SGCommandMgr::instance()->queuedExecute("cmd-a", args); + + // ensure args are cpatured during enqueue call + args->setIntValue("argA", 101); + } + + // not executed yet + SG_CHECK_EQUAL(test_rootNode->getIntValue("propA"), 71); + + { + SGPropertyNode_ptr args(new SGPropertyNode); + args->setIntValue("argB", 1234); + args->setBoolValue("result", true); + SGCommandMgr::instance()->queuedExecute("cmd-b", args); + } + + SGCommandMgr::instance()->executedQueuedCommands(); + + SG_CHECK_EQUAL(test_rootNode->getIntValue("propA"), 99); + SG_CHECK_EQUAL(test_rootNode->getIntValue("propB"), 1234); +} + +/////////////////////////////////////////////////////////////////////////////// + + +int main(int argc, char* argv[]) +{ + testBasicCommands(); + testQueuedExec(); + + cout << __FILE__ << ": All tests passed" << endl; + return EXIT_SUCCESS; +} From 9ebc823975190b92f3e582f0483c70537bafbfdc Mon Sep 17 00:00:00 2001 From: Tim Moore Date: Thu, 4 Apr 2019 11:38:44 +0000 Subject: [PATCH 30/33] Optimize parsing of property node names Add hand-coded versions of isalpha() and isdigit(), which were a hotspot in profiling. --- simgear/props/props.cxx | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/simgear/props/props.cxx b/simgear/props/props.cxx index 482d54ce..9d941559 100644 --- a/simgear/props/props.cxx +++ b/simgear/props/props.cxx @@ -90,6 +90,29 @@ struct PathComponent * Name: [_a-zA-Z][-._a-zA-Z0-9]* */ +namespace +{ +// Parsing property names is a profiling hotspot. The regular C +// library functions interact with the locale and are therefore quite +// heavyweight. We only support ASCII letters and numbers in property +// names, so use these simple functions instead. + +inline bool isalpha_c(int c) +{ + return (c <= 'Z' && c >= 'A') || (c <= 'z' && c >= 'a'); +} + +inline bool isdigit_c(int c) +{ + return c <= '9' && c >= '0'; +} + +inline bool isspecial_c(int c) +{ + return c == '_' || c == '-' || c == '.'; +} +} + template inline Range parse_name (const SGPropertyNode *node, const Range &path) @@ -104,14 +127,13 @@ parse_name (const SGPropertyNode *node, const Range &path) } if (i != max && *i != '/') throw std::string("illegal character after . or .."); - } else if (isalpha(*i) || *i == '_') { + } else if (isalpha_c(*i) || *i == '_') { i++; // The rules inside a name are a little // less restrictive. while (i != max) { - if (isalpha(*i) || isdigit(*i) || *i == '_' || - *i == '-' || *i == '.') { + if (isalpha_c(*i) || isdigit_c(*i) || isspecial_c(*i)) { // name += path[i]; } else if (*i == '[' || *i == '/') { break; @@ -157,8 +179,8 @@ inline bool validateName(const std::string& name) } return rv; #else - return all(make_iterator_range(name.begin(), name.end()), - is_alnum() || is_any_of("_-.")); + return std::all_of(name.begin() + 1, name.end(), + [](int c){ return isalpha_c(c) || isdigit_c(c) || isspecial_c(c); }); #endif } From 5f9b17d55b8859f46502f044d425ee8fbdb16303 Mon Sep 17 00:00:00 2001 From: Richard Harrison Date: Mon, 8 Apr 2019 14:08:58 +0200 Subject: [PATCH 31/33] Terrasync fixes; - remove the old svn properties; a scan of FGAddon and FGdata showed these are not in use except for the dialog - new property "active" - which is true when the worker thread is running. - changes to enabled now take effect immediately; whereas before a reinit had to be issued. This could lead to confusing GUI state as the enabled property would be true but without closing the dialog (e.g. cancelling it) terrasync could be in a different state. - use property nodes (performance optimisation) --- simgear/scene/tsync/terrasync.cxx | 78 ++++++++++++++++++++++--------- simgear/scene/tsync/terrasync.hxx | 11 +++++ 2 files changed, 67 insertions(+), 22 deletions(-) diff --git a/simgear/scene/tsync/terrasync.cxx b/simgear/scene/tsync/terrasync.cxx index 01385b0a..69e381f2 100644 --- a/simgear/scene/tsync/terrasync.cxx +++ b/simgear/scene/tsync/terrasync.cxx @@ -773,7 +773,7 @@ void SGTerraSync::WorkerThread::initCompletedTilesPersistentCache() try { readProperties(_persistentCachePath, cacheRoot); } catch (sg_exception& e) { - SG_LOG(SG_TERRASYNC, SG_INFO, "corrupted persistent cache, discarding"); + SG_LOG(SG_TERRASYNC, SG_INFO, "corrupted persistent cache, discarding " << e.getFormattedMessage()); return; } @@ -864,28 +864,31 @@ void SGTerraSync::init() _inited = true; assert(_terraRoot); - _terraRoot->setBoolValue("built-in-svn-available", true); reinit(); } void SGTerraSync::shutdown() { + SG_LOG(SG_TERRASYNC, SG_INFO, "Shutdown"); _workerThread->stop(); } void SGTerraSync::reinit() { + auto enabled = _enabledNode->getBoolValue(); // do not reinit when enabled and we're already up and running - if ((_terraRoot->getBoolValue("enabled",false)) && _workerThread->isRunning()) + if (enabled && _workerThread->isRunning()) { + _availableNode->setBoolValue(true); return; } - + _stalledNode->setBoolValue(false); _workerThread->stop(); - if (_terraRoot->getBoolValue("enabled",false)) + if (enabled) { + _availableNode->setBoolValue(true); _workerThread->setHTTPServer( _terraRoot->getStringValue("http-server","automatic") ); _workerThread->setSceneryVersion( _terraRoot->getStringValue("scenery-version","ws20") ); _workerThread->setProtocol( _terraRoot->getStringValue("protocol","") ); @@ -907,6 +910,8 @@ void SGTerraSync::reinit() syncAirportsModels(); } } + else + _availableNode->setBoolValue(false); _stalledNode->setBoolValue(_workerThread->isStalled()); } @@ -919,18 +924,33 @@ void SGTerraSync::bind() _bound = true; - _terraRoot->getNode("busy", true)->setAttribute(SGPropertyNode::WRITE,false); - _terraRoot->getNode("active", true)->setAttribute(SGPropertyNode::WRITE,false); - _terraRoot->getNode("update-count", true)->setAttribute(SGPropertyNode::WRITE,false); - _terraRoot->getNode("error-count", true)->setAttribute(SGPropertyNode::WRITE,false); - _terraRoot->getNode("tile-count", true)->setAttribute(SGPropertyNode::WRITE,false); - _terraRoot->getNode("use-built-in-svn", true)->setAttribute(SGPropertyNode::USERARCHIVE,false); - _terraRoot->getNode("use-svn", true)->setAttribute(SGPropertyNode::USERARCHIVE,false); + //_terraRoot->getNode("use-built-in-svn", true)->setAttribute(SGPropertyNode::USERARCHIVE,false); + //_terraRoot->getNode("use-svn", true)->setAttribute(SGPropertyNode::USERARCHIVE,false); + _terraRoot->getNode("intialized", true)->setBoolValue(true); // stalled is used as a signal handler (to connect listeners triggering GUI pop-ups) _stalledNode = _terraRoot->getNode("stalled", true); _stalledNode->setBoolValue(_workerThread->isStalled()); - _stalledNode->setAttribute(SGPropertyNode::PRESERVE,true); +// _stalledNode->setAttribute(SGPropertyNode::PRESERVE,true); + + _activeNode = _terraRoot->getNode("active", true); + + _busyNode = _terraRoot->getNode("busy", true); + _updateCountNode = _terraRoot->getNode("update-count", true); + _errorCountNode = _terraRoot->getNode("error-count", true); + _tileCountNode = _terraRoot->getNode("tile-count", true); + _cacheHitsNode = _terraRoot->getNode("cache-hits", true); + _transferRateBytesSecNode = _terraRoot->getNode("transfer-rate-bytes-sec", true); + _pendingKbytesNode = _terraRoot->getNode("pending-kbytes", true); + _downloadedKBtesNode = _terraRoot->getNode("downloaded-kbytes", true); + _enabledNode = _terraRoot->getNode("enabled", true); + _availableNode = _terraRoot->getNode("available", true); + //_busyNode->setAttribute(SGPropertyNode::WRITE, false); + //_activeNode->setAttribute(SGPropertyNode::WRITE, false); + //_updateCountNode->setAttribute(SGPropertyNode::WRITE, false); + //_errorCountNode->setAttribute(SGPropertyNode::WRITE, false); + //_tileCountNode->setAttribute(SGPropertyNode::WRITE, false); + } void SGTerraSync::unbind() @@ -947,19 +967,33 @@ void SGTerraSync::unbind() void SGTerraSync::update(double) { + auto enabled = _enabledNode->getBoolValue(); + auto worker_running = _workerThread->isRunning(); + + // see if the enabled status has changed; and if so take the appropriate action. + if (enabled && !worker_running) + { + reinit(); + SG_LOG(SG_TERRASYNC, SG_ALERT, "Terrasync started"); + } + else if (!enabled && worker_running) + { + reinit(); + SG_LOG(SG_TERRASYNC, SG_ALERT, "Terrasync stopped"); + } TerrasyncThreadState copiedState(_workerThread->threadsafeCopyState()); - _terraRoot->setBoolValue("busy", copiedState._busy); - _terraRoot->setIntValue("update-count", copiedState._success_count); - _terraRoot->setIntValue("error-count", copiedState._fail_count); - _terraRoot->setIntValue("tile-count", copiedState._updated_tile_count); - _terraRoot->setIntValue("cache-hits", copiedState._cache_hits); - _terraRoot->setIntValue("transfer-rate-bytes-sec", copiedState._transfer_rate); - _terraRoot->setIntValue("downloaded-kbytes", copiedState._total_kb_downloaded); - _terraRoot->setIntValue("pending-kbytes", copiedState._totalKbPending); + _busyNode->setIntValue(copiedState._busy); + _updateCountNode->setIntValue(copiedState._success_count); + _errorCountNode->setIntValue(copiedState._fail_count); + _tileCountNode->setIntValue(copiedState._updated_tile_count); + _cacheHitsNode->setIntValue(copiedState._cache_hits); + _transferRateBytesSecNode->setIntValue(copiedState._transfer_rate); + _pendingKbytesNode->setIntValue(copiedState._totalKbPending); + _downloadedKBtesNode->setIntValue(copiedState._total_kb_downloaded); _stalledNode->setBoolValue(_workerThread->isStalled()); - _terraRoot->setBoolValue("active", _workerThread->isRunning()); + _activeNode->setBoolValue(worker_running); while (_workerThread->hasNewTiles()) { diff --git a/simgear/scene/tsync/terrasync.hxx b/simgear/scene/tsync/terrasync.hxx index 78a3bd62..4e806568 100644 --- a/simgear/scene/tsync/terrasync.hxx +++ b/simgear/scene/tsync/terrasync.hxx @@ -92,6 +92,17 @@ private: SGPropertyNode_ptr _renderingRoot; SGPropertyNode_ptr _stalledNode; SGPropertyNode_ptr _cacheHits; + SGPropertyNode_ptr _busyNode; + SGPropertyNode_ptr _activeNode; + SGPropertyNode_ptr _enabledNode; + SGPropertyNode_ptr _availableNode; + SGPropertyNode_ptr _updateCountNode; + SGPropertyNode_ptr _errorCountNode; + SGPropertyNode_ptr _tileCountNode; + SGPropertyNode_ptr _cacheHitsNode; + SGPropertyNode_ptr _transferRateBytesSecNode; + SGPropertyNode_ptr _pendingKbytesNode; + SGPropertyNode_ptr _downloadedKBtesNode; // we manually bind+init TerraSync during early startup // to get better overlap of slow operations (Shared Models sync From 57339486d9491e902c963fbe6f97423f4a978676 Mon Sep 17 00:00:00 2001 From: James Turner Date: Thu, 11 Apr 2019 20:37:47 +0100 Subject: [PATCH 32/33] Flip the default meaning of alternate-mouse-wheel-dir --- simgear/scene/model/SGPickAnimation.cxx | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/simgear/scene/model/SGPickAnimation.cxx b/simgear/scene/model/SGPickAnimation.cxx index 6551500f..9b9f5249 100644 --- a/simgear/scene/model/SGPickAnimation.cxx +++ b/simgear/scene/model/SGPickAnimation.cxx @@ -582,20 +582,18 @@ public: } } } - - virtual bool buttonPressed( int button, - const osgGA::GUIEventAdapter& ea, - const Info& ) - { + + bool buttonPressed( int button, const osgGA::GUIEventAdapter& ea, const Info& ) override + { if (!_condition || _condition->test()) { // the 'be nice to Mac / laptop' users option; alt-clicking spins the - // opposite direction. Should make this configurable + // opposite direction. Should make this configurable if ((button == 0) && (ea.getModKeyMask() & osgGA::GUIEventAdapter::MODKEY_ALT)) { button = 1; } - int increaseMouseWheel = static_knobMouseWheelAlternateDirection ? 3 : 4; - int decreaseMouseWheel = static_knobMouseWheelAlternateDirection ? 4 : 3; + const int increaseMouseWheel = static_knobMouseWheelAlternateDirection ? 4 : 3; + const int decreaseMouseWheel = static_knobMouseWheelAlternateDirection ? 3 : 4; _direction = DIRECTION_NONE; if ((button == 0) || (button == increaseMouseWheel)) { From 114ddcff5236295688f8605426f6517bf65566e3 Mon Sep 17 00:00:00 2001 From: James Turner Date: Thu, 11 Apr 2019 21:39:41 +0100 Subject: [PATCH 33/33] Tweak code for older GCC --- simgear/scene/viewer/CompositorUtil.hxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simgear/scene/viewer/CompositorUtil.hxx b/simgear/scene/viewer/CompositorUtil.hxx index d5385908..2b0d47c9 100644 --- a/simgear/scene/viewer/CompositorUtil.hxx +++ b/simgear/scene/viewer/CompositorUtil.hxx @@ -27,7 +27,7 @@ namespace compositor { * represented in the property tree. Useful for OSG or OpenGL enums. */ template -using PropStringMap = const std::unordered_map; +using PropStringMap = std::unordered_map; template bool findPropString(const std::string &str,