Compositor framework initial commit.

This commit is contained in:
Fernando García Liñán
2019-02-04 17:59:18 +01:00
parent 4b793d5344
commit dc2f24dbed
13 changed files with 1932 additions and 8 deletions

View File

@@ -9,6 +9,7 @@ foreach( mylibfolder
tgdb
util
tsync
viewer
)
add_subdirectory(${mylibfolder})

View File

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

View File

@@ -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<std::string,osg::ref_ptr<osg::Texture2D> > _bufferList;
std::vector<osg::ref_ptr<EffectGeode> > _lightList;
bool _collectLights;
osg::ref_ptr<Effect> _effectOverride;
};
}
#endif

View File

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

View File

@@ -0,0 +1,209 @@
// Copyright (C) 2018 Fernando García Liñán <fernandogarcialinan@gmail.com>
//
// 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 <osg/BufferIndexBinding>
#include <osg/BufferObject>
#include <osg/RenderInfo>
#include <osg/Texture3D>
#include <osg/TextureBuffer>
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<osg::Texture3D> 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<osg::TextureBuffer> 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<osg::UniformBufferObject> light_data_ubo =
new osg::UniformBufferObject;
_light_data->setBufferObject(light_data_ubo.get());
osg::ref_ptr<osg::UniformBufferBinding> 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<osg::Polytope> 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<GLushort *>
(_light_grid->data());
GLushort *index_data = reinterpret_cast<GLushort *>
(_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

View File

@@ -0,0 +1,40 @@
// Copyright (C) 2018 Fernando García Liñán <fernandogarcialinan@gmail.com>
//
// 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 <osg/Camera>
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<osg::Image> _light_grid;
osg::ref_ptr<osg::Image> _light_indices;
osg::ref_ptr<osg::FloatArray> _light_data;
};
} // namespace compositor
} // namespace simgear
#endif /* SG_CLUSTERED_FORWARD_HXX */

View File

@@ -0,0 +1,313 @@
// Copyright (C) 2018 Fernando García Liñán <fernandogarcialinan@gmail.com>
//
// 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 <algorithm>
#include <osgUtil/IntersectionVisitor>
#include <osgViewer/Renderer>
#include <osgViewer/Viewer>
#include <simgear/math/SGRect.hxx>
#include <simgear/props/props_io.hxx>
#include <simgear/scene/material/EffectCullVisitor.hxx>
#include <simgear/scene/util/SGReaderWriterOptions.hxx>
#include <simgear/structure/exception.hxx>
#include <simgear/scene/util/RenderConstants.hxx>
namespace simgear {
namespace compositor {
Compositor *
Compositor::create(osg::View *view,
osg::GraphicsContext *gc,
osg::Viewport *viewport,
const SGPropertyNode *property_list)
{
osg::ref_ptr<Compositor> 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<osg::Uniform> 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<double> 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<LineSegmentIntersector> 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<osg::Camera*>(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<osgViewer::Renderer*>(pass->camera->getRenderer());
for (int i = 0; i < 2; ++i) {
osgUtil::SceneView* sceneView = renderer->getSceneView(i);
osg::ref_ptr<osgUtil::CullVisitor::Identifier> 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<Pass> &p) {
return p->name == name;
});
if (it == _passes.end())
return 0;
return (*it);
}
} // namespace compositor
} // namespace simgear

View File

@@ -0,0 +1,140 @@
// Copyright (C) 2018 Fernando García Liñán <fernandogarcialinan@gmail.com>
//
// 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 <unordered_map>
#include <vector>
// For osgUtil::LineSegmentIntersector::Intersections, which is a typedef.
#include <osgUtil/LineSegmentIntersector>
#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 <name>.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<osg::Uniform>,
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<std::string, osg::ref_ptr<Buffer>> BufferMap;
const BufferMap & getBufferMap() const { return _buffers; }
Buffer * getBuffer(const std::string &name) const;
typedef std::vector<osg::ref_ptr<Pass>> 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<osg::Viewport> _viewport;
std::string _name;
BufferMap _buffers;
PassList _passes;
BuiltinUniforms _uniforms;
};
} // namespace compositor
} // namespace simgear
#endif /* SG_COMPOSITOR_HXX */

View File

@@ -0,0 +1,213 @@
// Copyright (C) 2018 Fernando García Liñán <fernandogarcialinan@gmail.com>
//
// 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 <osg/Texture1D>
#include <osg/Texture2D>
#include <osg/Texture2DArray>
#include <osg/Texture2DMultisample>
#include <osg/Texture3D>
#include <osg/TextureRectangle>
#include <osg/TextureCubeMap>
#include <simgear/props/props.hxx>
#include <simgear/props/vectorPropTemplates.hxx>
#include <simgear/scene/util/OsgMath.hxx>
#include "Compositor.hxx"
#include "CompositorCommon.hxx"
namespace simgear {
namespace compositor {
struct BufferFormat {
GLint internal_format;
GLenum source_format;
GLenum source_type;
};
PropStringMap<BufferFormat> 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<osg::Texture::WrapMode> 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<osg::Texture::FilterMode> 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<osg::Texture::ShadowTextureMode> shadow_texture_mode_map = {
{"luminance", osg::Texture::LUMINANCE},
{"intensity", osg::Texture::INTENSITY},
{"alpha", osg::Texture::ALPHA}
};
PropStringMap<osg::Texture::ShadowCompareFunc> 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> 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<SGVec4d>());
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

View File

@@ -0,0 +1,46 @@
// Copyright (C) 2018 Fernando García Liñán <fernandogarcialinan@gmail.com>
//
// 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 <osg/Texture>
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<osg::Texture> 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 */

View File

@@ -0,0 +1,62 @@
// Copyright (C) 2018 Fernando García Liñán <fernandogarcialinan@gmail.com>
//
// 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 <unordered_map>
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<class T>
using PropStringMap = const std::unordered_map<std::string, T>;
template <class T>
bool findPropString(const std::string &str,
T &value,
const PropStringMap<T> &map)
{
auto itr = map.find(str);
if (itr == map.end())
return false;
value = itr->second;
return true;
}
template <class T>
bool findPropString(const SGPropertyNode *parent,
const std::string &child_name,
T &value,
const PropStringMap<T> &map)
{
const SGPropertyNode *child = parent->getNode(child_name);
if (child) {
if (findPropString<T>(child->getStringValue(), value, map))
return true;
}
return false;
}
} // namespace compositor
} // namespace simgear
#endif /* SG_COMPOSITOR_COMMON_HXX */

View File

@@ -0,0 +1,741 @@
// Copyright (C) 2018 Fernando García Liñán <fernandogarcialinan@gmail.com>
//
// 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 <osg/Depth>
#include <osg/Geode>
#include <osg/Geometry>
#include <osg/PolygonMode>
#include <osg/io_utils>
#include <simgear/props/vectorPropTemplates.hxx>
#include <simgear/scene/material/EffectGeode.hxx>
#include <simgear/scene/tgdb/userdata.hxx>
#include <simgear/scene/util/OsgMath.hxx>
#include <simgear/scene/util/SGUpdateVisitor.hxx>
#include <simgear/structure/exception.hxx>
#include "ClusteredForward.hxx"
#include "Compositor.hxx"
#include "CompositorCommon.hxx"
namespace simgear {
namespace compositor {
PropStringMap<osg::Camera::BufferComponent> 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> 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<SGVec4d>());
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<SGVec4d>());
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> 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<osg::Geode> 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<EffectGeode> 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<osg::StateSet> 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<QuadPassBuilder> 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<osg::Camera *>(node);
osgUtil::CullVisitor *cv = dynamic_cast<osgUtil::CullVisitor *>(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<const osg::Light *>(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<osg::Light> _light;
osg::ref_ptr<osg::Uniform> _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<osg::LightSource *>(&node);
if (light_source)
_light = light_source->getLight();
}
traverse(node);
}
osg::ref_ptr<osg::Light> getLight() const {
osg::ref_ptr<osg::Light> light_ref;
_light.lock(light_ref);
return light_ref;
}
protected:
std::string _name;
osg::observer_ptr<osg::Light> _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<osg::Light> 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<LightFinder> _light_finder;
float _near_m;
float _far_m;
osg::ref_ptr<osg::Uniform> _light_matrix_uniform;
osg::Vec2d _half_sm_size;
};
struct ShadowMapPassBuilder : public PassBuilder {
virtual Pass *build(Compositor *compositor, const SGPropertyNode *root) {
osg::ref_ptr<Pass> 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<ShadowMapPassBuilder> 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<osg::Vec3, osg::Vec3> 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> 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<ShadowMapUpdateCallback *>(
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<ScenePassBuilder> 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

View File

@@ -0,0 +1,134 @@
// Copyright (C) 2018 Fernando García Liñán <fernandogarcialinan@gmail.com>
//
// 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 <unordered_map>
#include <osg/Camera>
#include <osg/View>
#include <simgear/scene/material/Effect.hxx>
#include <simgear/structure/Singleton.hxx>
#include <simgear/props/condition.hxx>
#include <simgear/props/props.hxx>
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<osg::Camera> camera;
SGSharedPtr<SGCondition> condition;
/** If null, there is no effect override for this pass. */
osg::ref_ptr<Effect> 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<PassUpdateCallback> 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<std::string, SGSharedPtr<PassBuilder>> PassBuilderMap;
struct PassBuilderMapSingleton : public Singleton<PassBuilderMapSingleton> {
PassBuilderMap _map;
};
template <typename T>
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 <typename T>
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 */