Compositor: Effects used by a compositor now receive a proper SGReaderWriterOptions and other misc fixes

This commit is contained in:
Fernando García Liñán 2019-04-23 03:35:10 +02:00
parent 114ddcff52
commit 3a79b71e80
8 changed files with 176 additions and 119 deletions

View File

@ -49,9 +49,9 @@ Light LIGHT_LIST[NUM_LIGHTS] = {
};
///// END DEBUG
ClusteredForwardDrawCallback::ClusteredForwardDrawCallback() :
ClusteredForwardDrawCallback::ClusteredForwardDrawCallback(int tile_size) :
_initialized(false),
_tile_size(64),
_tile_size(tile_size),
_light_grid(new osg::Image),
_light_indices(new osg::Image),
_light_data(new osg::FloatArray(MAX_POINT_LIGHTS))
@ -105,15 +105,15 @@ ClusteredForwardDrawCallback::operator()(osg::RenderInfo &renderInfo) const
_light_data->setBufferObject(light_data_ubo.get());
#if OSG_VERSION_LESS_THAN(3,6,0)
osg::ref_ptr<osg::UniformBufferBinding> light_data_ubb =
osg::ref_ptr<osg::UniformBufferBinding> light_data_ubb =
new osg::UniformBufferBinding(0, light_data_ubo.get(),
0, MAX_POINT_LIGHTS * 8 * sizeof(GLfloat));
#else
osg::ref_ptr<osg::UniformBufferBinding> light_data_ubb =
osg::ref_ptr<osg::UniformBufferBinding> 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);
light_data_ubb->setDataVariance(osg::Object::DYNAMIC);
camera->getOrCreateStateSet()->setAttribute(
light_data_ubb.get(), osg::StateAttribute::ON);

View File

@ -24,7 +24,7 @@ namespace compositor {
class ClusteredForwardDrawCallback : public osg::Camera::DrawCallback {
public:
ClusteredForwardDrawCallback();
ClusteredForwardDrawCallback(int tile_size);
virtual void operator()(osg::RenderInfo &renderInfo) const;
protected:
mutable bool _initialized;

View File

@ -39,7 +39,8 @@ Compositor *
Compositor::create(osg::View *view,
osg::GraphicsContext *gc,
osg::Viewport *viewport,
const SGPropertyNode *property_list)
const SGPropertyNode *property_list,
const SGReaderWriterOptions *options)
{
osg::ref_ptr<Compositor> compositor = new Compositor(view, gc, viewport);
compositor->_name = property_list->getStringValue("name");
@ -55,7 +56,7 @@ Compositor::create(osg::View *view,
"a name to be available to passes. Skipping...");
continue;
}
Buffer *buffer = buildBuffer(compositor.get(), p_buffer);
Buffer *buffer = buildBuffer(compositor.get(), p_buffer, options);
if (buffer)
compositor->addBuffer(buffer_name, buffer);
}
@ -64,7 +65,7 @@ Compositor::create(osg::View *view,
for (auto const &p_pass : p_passes) {
if (!checkConditional(p_pass))
continue;
Pass *pass = buildPass(compositor.get(), p_pass);
Pass *pass = buildPass(compositor.get(), p_pass, options);
if (pass)
compositor->addPass(pass);
}
@ -76,7 +77,8 @@ Compositor *
Compositor::create(osg::View *view,
osg::GraphicsContext *gc,
osg::Viewport *viewport,
const std::string &name)
const std::string &name,
const SGReaderWriterOptions *options)
{
std::string filename(name);
filename += ".xml";
@ -96,7 +98,7 @@ Compositor::create(osg::View *view,
return 0;
}
return create(view, gc, viewport, property_list);
return create(view, gc, viewport, property_list, options);
}
Compositor::Compositor(osg::View *view,

View File

@ -73,7 +73,8 @@ public:
static Compositor *create(osg::View *view,
osg::GraphicsContext *gc,
osg::Viewport *viewport,
const SGPropertyNode *property_list);
const SGPropertyNode *property_list,
const SGReaderWriterOptions *options);
/**
* \overload
* \brief Create a Compositor from a file.
@ -84,7 +85,8 @@ public:
static Compositor *create(osg::View *view,
osg::GraphicsContext *gc,
osg::Viewport *viewport,
const std::string &name);
const std::string &name,
const SGReaderWriterOptions *options);
void update(const osg::Matrix &view_matrix,
const osg::Matrix &proj_matrix);
@ -95,9 +97,11 @@ public:
const osg::Vec2d& windowPos,
osgUtil::LineSegmentIntersector::Intersections& intersections);
const osg::GraphicsContext *getGraphicsContext() const { return _gc; }
osg::View *getView() const { return _view; }
const osg::Viewport *getViewport() const { return _viewport; }
osg::GraphicsContext *getGraphicsContext() const { return _gc; }
osg::Viewport *getViewport() const { return _viewport; }
typedef std::array<
osg::ref_ptr<osg::Uniform>,
@ -121,8 +125,6 @@ public:
Pass * getPass(const std::string &name) const;
protected:
friend class PassBuilder;
osg::View *_view;
osg::GraphicsContext *_gc;
osg::ref_ptr<osg::Viewport> _viewport;

View File

@ -28,6 +28,7 @@
#include <simgear/props/props.hxx>
#include <simgear/props/vectorPropTemplates.hxx>
#include <simgear/scene/util/OsgMath.hxx>
#include <simgear/scene/util/SGReaderWriterOptions.hxx>
#include "Compositor.hxx"
#include "CompositorUtil.hxx"
@ -49,6 +50,7 @@ PropStringMap<BufferFormat> buffer_format_map {
{"rgba16f", {GL_RGBA16F_ARB, GL_RGBA, GL_FLOAT}},
{"rgba32f", {GL_RGBA32F_ARB, GL_RGBA, GL_FLOAT}},
{"r32f", {GL_R32F, GL_RED, GL_FLOAT}},
{"rg16f", {GL_RG16F, GL_RG, 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}},
@ -92,7 +94,8 @@ PropStringMap<osg::Texture::ShadowCompareFunc> shadow_compare_func_map = {
};
Buffer *
buildBuffer(Compositor *compositor, const SGPropertyNode *node)
buildBuffer(Compositor *compositor, const SGPropertyNode *node,
const SGReaderWriterOptions *options)
{
std::string type = node->getStringValue("type");
if (type.empty()) {

View File

@ -22,6 +22,9 @@
class SGPropertyNode;
namespace simgear {
class SGReaderWriterOptions;
namespace compositor {
class Compositor;
@ -38,7 +41,8 @@ struct Buffer : public osg::Referenced {
float width_scale, height_scale;
};
Buffer *buildBuffer(Compositor *compositor, const SGPropertyNode *node);
Buffer *buildBuffer(Compositor *compositor, const SGPropertyNode *node,
const SGReaderWriterOptions *options);
} // namespace compositor
} // namespace simgear

View File

@ -22,9 +22,12 @@
#include <osg/PolygonMode>
#include <osg/io_utils>
#include <osgUtil/CullVisitor>
#include <simgear/props/vectorPropTemplates.hxx>
#include <simgear/scene/material/EffectGeode.hxx>
#include <simgear/scene/util/OsgMath.hxx>
#include <simgear/scene/util/SGReaderWriterOptions.hxx>
#include <simgear/scene/util/SGUpdateVisitor.hxx>
#include <simgear/structure/exception.hxx>
@ -50,34 +53,40 @@ PropStringMap<osg::Camera::BufferComponent> buffer_component_map = {
{"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();
Pass *
PassBuilder::build(Compositor *compositor, const SGPropertyNode *root,
const SGReaderWriterOptions *options)
{
osg::ref_ptr<Pass> pass = new Pass;
// The pass index matches its render order
pass->render_order = root->getIndex();
pass->name = root->getStringValue("name");
if (pass->name.empty()) {
SG_LOG(SG_INPUT, SG_WARN, "PassBuilder::build: Pass " << render_order
SG_LOG(SG_INPUT, SG_WARN, "PassBuilder::build: Pass " << pass->render_order
<< " has no name. It won't be addressable by name!");
}
pass->type = root->getStringValue("type");
std::string eff_override_file = root->getStringValue("effect-override");
if (!eff_override_file.empty())
pass->effect_override = makeEffect(eff_override_file, true, 0);
pass->effect_override = makeEffect(eff_override_file, true, options);
osg::Camera *camera = new Camera;
pass->camera = camera;
camera->setName(pass->name);
camera->setGraphicsContext(compositor->_gc);
camera->setGraphicsContext(compositor->getGraphicsContext());
// 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);
// We only use POST_RENDER. Leave PRE_RENDER for Canvas and other RTT stuff
// that doesn't involve the rendering pipeline itself. NESTED_RENDER is also
// not a possibility since we don't want to share RenderStage with the View
// master camera.
camera->setRenderOrder(osg::Camera::POST_RENDER, pass->render_order * 10);
camera->setComputeNearFarMode(osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR);
// XXX: Should we make this configurable?
@ -144,7 +153,7 @@ PassBuilder::build(Compositor *compositor, const SGPropertyNode *root)
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
<< p_binding->getIndex() << " in pass " << pass->render_order
<< ": " << e.what());
}
}
@ -153,22 +162,15 @@ PassBuilder::build(Compositor *compositor, const SGPropertyNode *root)
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);
camera->setViewport(compositor->getViewport());
} 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;
@ -252,13 +254,13 @@ PassBuilder::build(Compositor *compositor, const SGPropertyNode *root)
camera->setViewport(
0,
0,
buffer->width_scale * compositor->_viewport->width(),
buffer->height_scale * compositor->_viewport->height());
buffer->width_scale * compositor->getViewport()->width(),
buffer->height_scale * compositor->getViewport()->height());
}
}
} catch (sg_exception &e) {
SG_LOG(SG_INPUT, SG_ALERT, "PassBuilder::build: Skipping attachment "
<< p_attachment->getIndex() << " in pass " << render_order
<< p_attachment->getIndex() << " in pass " << pass->render_order
<< ": " << e.what());
}
}
@ -271,8 +273,10 @@ PassBuilder::build(Compositor *compositor, const SGPropertyNode *root)
struct QuadPassBuilder : public PassBuilder {
public:
virtual Pass *build(Compositor *compositor, const SGPropertyNode *root) {
osg::ref_ptr<Pass> pass = PassBuilder::build(compositor, root);
virtual Pass *build(Compositor *compositor, const SGPropertyNode *root,
const SGReaderWriterOptions *options) {
osg::ref_ptr<Pass> pass = PassBuilder::build(compositor, root, options);
pass->useMastersSceneData = false;
osg::Camera *camera = pass->camera;
camera->setAllowEventFocus(false);
@ -289,11 +293,29 @@ public:
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);
osg::ref_ptr<EffectGeode> quad = new EffectGeode;
camera->addChild(quad);
quad->setCullingActive(false);
const std::string eff_file = root->getStringValue("effect");
if (!eff_file.empty()) {
Effect *eff = makeEffect(eff_file, true, options);
if (eff)
quad->setEffect(eff);
}
osg::ref_ptr<osg::Geometry> geom = createFullscreenQuadGeom(
left, bottom, width, height, scale);
quad->addDrawable(geom);
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);
osg::StateSet *ss = camera->getOrCreateStateSet();
for (const auto &uniform : compositor->getUniforms())
@ -302,13 +324,12 @@ public:
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;
osg::Geometry *createFullscreenQuadGeom(float left,
float bottom,
float width,
float height,
float scale) {
osg::ref_ptr<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
@ -349,31 +370,52 @@ protected:
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();
return geom.release();
}
};
RegisterPassBuilder<QuadPassBuilder> registerQuadPass("quad");
//------------------------------------------------------------------------------
class ShadowMapCullCallback : public osg::NodeCallback {
public:
ShadowMapCullCallback(const std::string &suffix) {
_light_matrix_uniform = new osg::Uniform(
osg::Uniform::FLOAT_MAT4, std::string("fg_LightMatrix_") + suffix);
}
virtual void operator()(osg::Node *node, osg::NodeVisitor *nv) {
osg::Camera *camera = static_cast<osg::Camera *>(node);
traverse(node, nv);
// The light matrix uniform is updated after the traverse in case the
// OSG near/far plane calculations were enabled
osg::Matrixf light_matrix =
// Include the real camera inverse view matrix because if the shader
// used world coordinates, there would be precision issues.
_real_inverse_view *
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);
}
void setRealInverseViewMatrix(const osg::Matrix &matrix) {
_real_inverse_view = matrix;
}
osg::Uniform *getLightMatrixUniform() const {
return _light_matrix_uniform.get();
}
protected:
osg::Matrix _real_inverse_view;
osg::ref_ptr<osg::Uniform> _light_matrix_uniform;
};
class LightFinder : public osg::NodeVisitor {
public:
LightFinder(const std::string &name) :
@ -406,15 +448,14 @@ protected:
struct ShadowMapUpdateCallback : public Pass::PassUpdateCallback {
public:
ShadowMapUpdateCallback(const std::string &light_name,
ShadowMapUpdateCallback(ShadowMapCullCallback *cull_callback,
const std::string &light_name,
float near_m, float far_m,
const std::string &suffix,
int sm_width, int sm_height) :
_cull_callback(cull_callback),
_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,
@ -443,6 +484,7 @@ public:
// This is not a problem though (for now).
osg::Matrix view_inverse = osg::Matrix::inverse(view_matrix);
_cull_callback->setRealInverseViewMatrix(view_inverse);
// Calculate the light's point of view transformation matrices.
// Taken from Project Rembrandt.
@ -489,37 +531,30 @@ public:
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::observer_ptr<ShadowMapCullCallback> _cull_callback;
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);
pass->useMastersSceneData = true;
virtual Pass *build(Compositor *compositor, const SGPropertyNode *root,
const SGReaderWriterOptions *options) {
osg::ref_ptr<Pass> pass = PassBuilder::build(compositor, root, options);
osg::Camera *camera = pass->camera;
camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF_INHERIT_VIEWPOINT);
camera->setCullingMode(camera->getCullingMode() &
~osg::CullSettings::SMALL_FEATURE_CULLING);
//camera->setComputeNearFarMode(
// osg::CullSettings::COMPUTE_NEAR_FAR_USING_BOUNDING_VOLUMES);
ShadowMapCullCallback *cull_callback = new ShadowMapCullCallback(pass->name);
camera->setCullCallback(cull_callback);
std::string light_name = root->getStringValue("light-name");
float near_m = root->getFloatValue("near-m");
@ -527,9 +562,9 @@ struct ShadowMapPassBuilder : public PassBuilder {
int sm_width = camera->getViewport()->width();
int sm_height = camera->getViewport()->height();
pass->update_callback = new ShadowMapUpdateCallback(
cull_callback,
light_name,
near_m, far_m,
pass->name,
sm_width, sm_height);
return pass.release();
@ -626,9 +661,9 @@ protected:
struct ScenePassBuilder : public PassBuilder {
public:
virtual Pass *build(Compositor *compositor, const SGPropertyNode *root) {
osg::ref_ptr<Pass> pass = PassBuilder::build(compositor, root);
pass->useMastersSceneData = true;
virtual Pass *build(Compositor *compositor, const SGPropertyNode *root,
const SGReaderWriterOptions *options) {
osg::ref_ptr<Pass> pass = PassBuilder::build(compositor, root, options);
pass->inherit_cull_mask = true;
osg::Camera *camera = pass->camera;
@ -636,7 +671,8 @@ public:
const SGPropertyNode *clustered = root->getChild("clustered-forward");
if (clustered) {
camera->setInitialDrawCallback(new ClusteredForwardDrawCallback);
int tile_size = clustered->getIntValue("tile-size", 64);
camera->setInitialDrawCallback(new ClusteredForwardDrawCallback(tile_size));
}
int cubemap_face = root->getIntValue("cubemap-face", -1);
@ -644,23 +680,26 @@ public:
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());
PropertyList p_shadow_passes = root->getChildren("use-shadow-pass");
for (const auto &p_shadow_pass : p_shadow_passes) {
std::string shadow_pass_name = p_shadow_pass->getStringValue();
if (!shadow_pass_name.empty()) {
Pass *shadow_pass = compositor->getPass(shadow_pass_name);
if (shadow_pass) {
ShadowMapCullCallback *cullcb =
dynamic_cast<ShadowMapCullCallback *>(
shadow_pass->camera->getCullCallback());
if (cullcb) {
camera->getOrCreateStateSet()->addUniform(
cullcb->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: Pass '"
<< shadow_pass_name << "is not a shadow pass");
SG_LOG(SG_INPUT, SG_WARN, "ScenePassBuilder::build: Could not "
"find shadow pass named '" << shadow_pass_name << "'");
}
} else {
SG_LOG(SG_INPUT, SG_WARN, "ScenePassBuilder::build: Could not "
"find shadow pass named '" << shadow_pass_name << "'");
}
}
@ -673,7 +712,8 @@ RegisterPassBuilder<ScenePassBuilder> registerScenePass("scene");
//------------------------------------------------------------------------------
Pass *
buildPass(Compositor *compositor, const SGPropertyNode *root)
buildPass(Compositor *compositor, const SGPropertyNode *root,
const SGReaderWriterOptions *options)
{
std::string type = root->getStringValue("type");
if (type.empty()) {
@ -687,7 +727,7 @@ buildPass(Compositor *compositor, const SGPropertyNode *root)
return 0;
}
return builder->build(compositor, root);
return builder->build(compositor, root, options);
}
} // namespace compositor

View File

@ -27,6 +27,9 @@
#include <simgear/props/props.hxx>
namespace simgear {
class SGReaderWriterOptions;
namespace compositor {
class Compositor;
@ -45,12 +48,13 @@ class Compositor;
*/
struct Pass : public osg::Referenced {
Pass() :
useMastersSceneData(false),
useMastersSceneData(true),
cull_mask(0xffffff),
inherit_cull_mask(false),
viewport_width_scale(0.0f),
viewport_height_scale(0.0f) {}
int render_order;
std::string name;
std::string type;
osg::ref_ptr<osg::Camera> camera;
@ -85,10 +89,11 @@ public:
* and overrided for more special passes.
*
* @param compositor The Compositor instance that owns the pass.
* @param The root node of the pass property tree.
* @param root 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);
virtual Pass *build(Compositor *compositor, const SGPropertyNode *root,
const SGReaderWriterOptions *options);
static PassBuilder *find(const std::string &type) {
auto itr = PassBuilderMapSingleton::instance()->_map.find(type);
@ -125,7 +130,8 @@ struct RegisterPassBuilder {
* @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);
Pass *buildPass(Compositor *compositor, const SGPropertyNode *root,
const SGReaderWriterOptions *options);
} // namespace compositor
} // namespace simgear