--- /dev/null
+#include "meshbuilder.h"
+#include "sidebysidecombiner.h"
+#include "texture2d.h"
+
+namespace {
+
+const char vs_source[] =
+ "uniform float offset;\n"
+ "varying vec2 texcoord;\n"
+ "void main()\n"
+ "{\n"
+ " gl_Position = vec4(gl_Vertex.x*0.5+offset, gl_Vertex.yzw);\n"
+ " texcoord = gl_Vertex.xy*0.5+0.5;\n"
+ "}\n";
+
+const char fs_source[] =
+ "uniform sampler2D texture;\n"
+ "varying vec2 texcoord;\n"
+ "void main()\n"
+ "{\n"
+ " gl_FragColor = texture2D(texture, texcoord);\n"
+ "}\n";
+
+}
+
+namespace Msp {
+namespace GL {
+
+SideBySideCombiner::SideBySideCombiner(bool c):
+ mesh(VERTEX2),
+ shprog(vs_source, fs_source)
+{
+ width_div = 2;
+
+ left_shdata.uniform("texture", 0);
+ right_shdata.uniform("texture", 0);
+
+ set_cross_eyed(c);
+
+ MeshBuilder bld(mesh);
+ bld.begin(TRIANGLE_STRIP);
+ bld.vertex(-1, 1);
+ bld.vertex(-1, -1);
+ bld.vertex(1, 1);
+ bld.vertex(1, -1);
+ bld.end();
+}
+
+void SideBySideCombiner::set_cross_eyed(bool c)
+{
+ cross_eyed = c;
+ float m = (cross_eyed ? -0.5f : 0.5f);
+ left_shdata.uniform("offset", -m);
+ right_shdata.uniform("offset", m);
+}
+
+void SideBySideCombiner::render(const Texture2D &left, const Texture2D &right) const
+{
+ Bind bind_shprog(shprog);
+
+ Bind bind_tex(left);
+ left_shdata.apply();
+ mesh.draw();
+
+ right.bind();
+ right_shdata.apply();
+ mesh.draw();
+}
+
+} // namespace GL
+} // namespace Msp
--- /dev/null
+#ifndef MSP_GL_SIDEBYSIDECOMBINER_H_
+#define MSP_GL_SIDEBYSIDECOMBINER_H_
+
+#include "mesh.h"
+#include "program.h"
+#include "programdata.h"
+#include "stereocombiner.h"
+
+namespace Msp {
+namespace GL {
+
+class SideBySideCombiner: public StereoCombiner
+{
+private:
+ Mesh mesh;
+ Program shprog;
+ ProgramData left_shdata;
+ ProgramData right_shdata;
+ bool cross_eyed;
+
+public:
+ SideBySideCombiner(bool = false);
+
+ void set_cross_eyed(bool);
+
+ virtual void render(const Texture2D &, const Texture2D &) const;
+};
+
+} // namespace GL
+} // namespace Msp
+
+#endif
--- /dev/null
+#include "stereocombiner.h"
+
+namespace Msp {
+namespace GL {
+
+StereoCombiner::StereoCombiner():
+ width_div(1),
+ height_div(1),
+ keep_aspect(false),
+ fov(0)
+{ }
+
+} // namespace GL
+} // namespace Msp
--- /dev/null
+#ifndef MSP_GL_STEREOCOMBINER_H_
+#define MSP_GL_STEREOCOMBINER_H_
+
+namespace Msp {
+namespace GL {
+
+class Texture2D;
+
+class StereoCombiner
+{
+protected:
+ unsigned width_div;
+ unsigned height_div;
+ bool keep_aspect;
+ float fov;
+
+ StereoCombiner();
+public:
+ virtual ~StereoCombiner() { }
+
+ unsigned get_width_divisor() const { return width_div; }
+ unsigned get_height_divisor() const { return height_div; }
+ bool is_aspect_kept() const { return keep_aspect; }
+ float get_field_of_view() const { return fov; }
+
+ virtual void render(const Texture2D &, const Texture2D &) const = 0;
+};
+
+} // namespace GL
+} // namespace Msp
+
+#endif
--- /dev/null
+#include "renderer.h"
+#include "stereocombiner.h"
+#include "stereoview.h"
+
+using namespace std;
+
+namespace Msp {
+namespace GL {
+
+StereoView::StereoView(unsigned w, unsigned h, const Camera &c, const Renderable &r, const StereoCombiner &m):
+ width(w),
+ height(h),
+ base_camera(c),
+ renderable(r),
+ combiner(0)
+{
+ set_combiner(m);
+ set_eye_spacing(0.07);
+}
+
+void StereoView::set_combiner(const StereoCombiner &c)
+{
+ combiner = &c;
+
+ unsigned w = width/combiner->get_width_divisor();
+ unsigned h = height/combiner->get_height_divisor();
+ left.create_target(w, h);
+ right.create_target(w, h);
+}
+
+void StereoView::set_eye_spacing(float s)
+{
+ eye_spacing = s;
+}
+
+void StereoView::setup_frame() const
+{
+ offset_axis = normalize(cross(base_camera.get_look_direction(), base_camera.get_up_direction()))*0.5f;
+
+ EyeParams params;
+ params.fov = combiner->get_field_of_view();
+ if(!params.fov)
+ params.fov = base_camera.get_field_of_view();
+
+ params.aspect = base_camera.get_aspect();
+ if(!combiner->is_aspect_kept())
+ params.aspect = params.aspect*combiner->get_height_divisor()/combiner->get_width_divisor();
+
+ params.near_clip = base_camera.get_near_clip();
+ params.far_clip = base_camera.get_far_clip();
+
+ left.setup_frame(base_camera, offset_axis*-eye_spacing, params);
+ right.setup_frame(base_camera, offset_axis*eye_spacing, params);
+
+ renderable.setup_frame();
+}
+
+void StereoView::finish_frame() const
+{
+ renderable.finish_frame();
+}
+
+void StereoView::render(const Tag &tag) const
+{
+ setup_frame();
+ left.render(renderable, tag);
+ right.render(renderable, tag);
+ combiner->render(left.target->color, right.target->color);
+ finish_frame();
+}
+
+void StereoView::render(Renderer &renderer, const Tag &tag) const
+{
+ renderer.escape();
+ return render(tag);
+}
+
+
+StereoView::RenderTarget::RenderTarget(unsigned width, unsigned height)
+{
+ color.set_min_filter(LINEAR);
+ color.set_wrap(CLAMP_TO_EDGE);
+ color.storage(RGB, width, height);
+ fbo.attach(COLOR_ATTACHMENT0, color);
+
+ depth.storage(DEPTH_COMPONENT, width, height);
+ fbo.attach(DEPTH_ATTACHMENT, depth);
+}
+
+
+StereoView::Eye::Eye():
+ target(0)
+{ }
+
+void StereoView::Eye::create_target(unsigned w, unsigned h)
+{
+ delete target;
+ target = new RenderTarget(w, h);
+}
+
+void StereoView::Eye::setup_frame(const Camera &base_camera, const Vector3 &offset, const EyeParams ¶ms) const
+{
+ camera.set_position(base_camera.get_position()+offset);
+ camera.set_up_direction(base_camera.get_up_direction());
+ camera.set_look_direction(base_camera.get_look_direction());
+
+ camera.set_field_of_view(params.fov);
+ camera.set_aspect(params.aspect);
+ camera.set_depth_clip(params.near_clip, params.far_clip);
+}
+
+void StereoView::Eye::render(const Renderable &renderable, const Tag &tag) const
+{
+ Bind bind_fbo(target->fbo);
+ Renderer renderer(&camera);
+ renderable.render(renderer, tag);
+}
+
+} // namespace GL
+} // namespace Msp
--- /dev/null
+#ifndef MSP_GL_STEREOVIEW_H_
+#define MSP_GL_STEREOVIEW_H_
+
+#include "camera.h"
+#include "framebuffer.h"
+#include "renderable.h"
+#include "renderbuffer.h"
+#include "texture2d.h"
+
+namespace Msp {
+namespace GL {
+
+class StereoCombiner;
+
+class StereoView: public Renderable
+{
+private:
+ struct RenderTarget
+ {
+ Framebuffer fbo;
+ Texture2D color;
+ Renderbuffer depth;
+
+ RenderTarget(unsigned, unsigned);
+ };
+
+ struct EyeParams
+ {
+ float fov;
+ float aspect;
+ float near_clip;
+ float far_clip;
+ };
+
+ struct Eye
+ {
+ mutable Camera camera;
+ RenderTarget *target;
+
+ Eye();
+
+ void create_target(unsigned, unsigned);
+ void setup_frame(const Camera &, const Vector3 &, const EyeParams &) const;
+ void render(const Renderable &, const Tag &) const;
+ };
+
+ unsigned width;
+ unsigned height;
+ const Camera &base_camera;
+ const Renderable &renderable;
+ const StereoCombiner *combiner;
+ Eye left;
+ Eye right;
+ float eye_spacing;
+ mutable Vector3 offset_axis;
+
+public:
+ StereoView(unsigned, unsigned, const Camera &, const Renderable &, const StereoCombiner &);
+
+ void set_combiner(const StereoCombiner &);
+ void set_eye_spacing(float);
+
+ virtual void setup_frame() const;
+ virtual void finish_frame() const;
+
+ virtual void render(const Tag & = Tag()) const;
+ virtual void render(Renderer &, const Tag & = Tag()) const;
+};
+
+} // namespace GL
+} // namespace Msp
+
+#endif