--- /dev/null
+#include <algorithm>
+#include <msp/gl/extensions/arb_occlusion_query.h>
+#include "camera.h"
+#include "occludedscene.h"
+#include "renderer.h"
+#include "sphere.h"
+
+using namespace std;
+
+namespace {
+
+const char vshader[] =
+ "void main()\n"
+ "{\n"
+ " gl_Position = gl_ProjectionMatrix*gl_ModelViewMatrix*gl_Vertex;\n"
+ "}";
+
+const char fshader[] =
+ "void main()\n"
+ "{\n"
+ " gl_FragColor = vec4(1.0);\n"
+ "}";
+
+}
+
+namespace Msp {
+namespace GL {
+
+OccludedScene::OccludedScene():
+ bounding_mesh((VERTEX3, NORMAL3)),
+ bounding_shader(vshader, fshader),
+ occluder_min_size(0.25f),
+ cache_dirty(false)
+{
+ static Require req(ARB_occlusion_query);
+
+ /* Use a slightly larger radius to ensure that all parts of the renderable
+ fit inside the icosahedron */
+ IcoSphereBuilder(1.26f, 1).build(bounding_mesh);
+ bounding_mesh.set_winding(&WindingTest::counterclockwise());
+}
+
+OccludedScene::~OccludedScene()
+{
+ vector<unsigned> queries;
+ queries.reserve(occluded_cache.size());
+ for(OccludedArray::iterator i=occluded_cache.begin(); i!=occluded_cache.end(); ++i)
+ queries.push_back(i->query);
+ glDeleteQueries(queries.size(), &queries[0]);
+}
+
+void OccludedScene::add(const Renderable &r)
+{
+ renderables.insert(&r);
+ cache_dirty = true;
+}
+
+void OccludedScene::remove(const Renderable &r)
+{
+ renderables.erase(&r);
+ cache_dirty = true;
+}
+
+void OccludedScene::render(Renderer &renderer, const Tag &tag) const
+{
+ if(renderables.empty())
+ return;
+
+ const Camera *camera = renderer.get_camera();
+ if(!camera)
+ {
+ for(RenderableSet::const_iterator i=renderables.begin(); i!=renderables.end(); ++i)
+ renderer.render(**i, tag);
+ return;
+ }
+
+ if(cache_dirty)
+ {
+ if(occluded_cache.size()<renderables.size())
+ {
+ unsigned old_size = occluded_cache.size();
+ occluded_cache.resize(renderables.size());
+ vector<unsigned> new_queries(occluded_cache.size()-old_size);
+ glGenQueries(new_queries.size(), &new_queries[0]);
+ for(unsigned i=0; i<new_queries.size(); ++i)
+ occluded_cache[old_size+i].query = new_queries[i];
+ }
+
+ OccludedArray::iterator j = occluded_cache.begin();
+ for(RenderableSet::iterator i=renderables.begin(); i!=renderables.end(); ++i, ++j)
+ j->renderable = *i;
+ for(; j!=occluded_cache.end(); ++j)
+ {
+ j->renderable = 0;
+ j->in_frustum = false;
+ }
+
+ cache_dirty = false;
+ }
+
+ const Vector3 &camera_pos = camera->get_position();
+ const Vector3 &look_dir = camera->get_look_direction();
+ float near_clip = camera->get_near_clip();
+ float frustum_h = tan(camera->get_field_of_view()/2.0f)*2.0f;
+
+ // Perform frustum culling and render any major occluders
+ bool use_frustum = setup_frustum(renderer);
+ for(OccludedArray::iterator i=occluded_cache.begin(); (i!=occluded_cache.end() && i->renderable); ++i)
+ {
+ i->in_frustum = (!use_frustum || !frustum_cull(*i->renderable));
+ if(!i->in_frustum)
+ continue;
+
+ const Matrix *matrix = i->renderable->get_matrix();
+ i->bounding_sphere = i->renderable->get_bounding_sphere();
+ if(matrix && i->bounding_sphere)
+ {
+ float depth = dot(*matrix*i->bounding_sphere->get_center()-camera_pos, look_dir);
+ float size = i->bounding_sphere->get_radius()*2/max(depth, near_clip);
+ i->occluder = (size>frustum_h*occluder_min_size);
+ }
+ else
+ // If the size can't be calculated, treat it as occluder
+ i->occluder = true;
+
+ if(i->occluder)
+ renderer.render(*i->renderable, tag);
+ }
+
+ // Move all objects within the frustum to the beginning of the array
+ for(OccludedArray::iterator i=occluded_cache.begin(), j=i+renderables.size()-1; i!=j; )
+ {
+ if(i->in_frustum)
+ ++i;
+ else if(j->in_frustum)
+ swap(*i, *j);
+ else
+ --j;
+ }
+
+ {
+ Renderer::Push push(renderer);
+ renderer.set_shader_program(&bounding_shader);
+
+ glColorMask(false, false, false, false);
+ glDepthMask(false);
+
+ // Fire off the occlusion queries
+ for(OccludedArray::const_iterator i=occluded_cache.begin(); (i!=occluded_cache.end() && i->in_frustum); ++i)
+ if(!i->occluder)
+ {
+ glBeginQuery(GL_SAMPLES_PASSED, i->query);
+ Renderer::Push push2(renderer);
+ renderer.transform(Matrix(*i->renderable->get_matrix())
+ .translate(i->bounding_sphere->get_center())
+ .scale(i->bounding_sphere->get_radius()));
+ bounding_mesh.draw(renderer);
+ glEndQuery(GL_SAMPLES_PASSED);
+ }
+
+ glColorMask(true, true, true, true);
+ glDepthMask(true);
+ }
+
+ // Render anything that has a chance of being visible
+ for(OccludedArray::const_iterator i=occluded_cache.begin(); (i!=occluded_cache.end() && i->in_frustum); ++i)
+ if(!i->occluder)
+ {
+ int samples_passed;
+ glGetQueryObjectiv(i->query, GL_QUERY_RESULT, &samples_passed);
+ if(samples_passed>0)
+ renderer.render(*i->renderable, tag);
+ }
+}
+
+
+OccludedScene::OccludedRenderable::OccludedRenderable():
+ renderable(0),
+ bounding_sphere(0),
+ in_frustum(false),
+ occluder(false),
+ query(0)
+{ }
+
+} // namespace GL
+} // namespace Msp
--- /dev/null
+#ifndef MSP_GL_OCCLUDEDSCENE_H_
+#define MSP_GL_OCCLUDEDSCENE_H_
+
+#include <set>
+#include <vector>
+#include "mesh.h"
+#include "program.h"
+#include "scene.h"
+
+namespace Msp {
+namespace GL {
+
+/**
+A scene that performs occlusion queries on renderables to skip those that are
+entirely occluded by others.
+*/
+class OccludedScene: public Scene
+{
+private:
+ struct OccludedRenderable
+ {
+ const Renderable *renderable;
+ const Geometry::BoundingSphere<float, 3> *bounding_sphere;
+ bool in_frustum;
+ bool occluder;
+ unsigned query;
+
+ OccludedRenderable();
+ };
+
+ typedef std::set<const Renderable *> RenderableSet;
+ typedef std::vector<OccludedRenderable> OccludedArray;
+
+ Mesh bounding_mesh;
+ Program bounding_shader;
+ RenderableSet renderables;
+ float occluder_min_size;
+ mutable OccludedArray occluded_cache;
+ mutable bool cache_dirty;
+
+public:
+ OccludedScene();
+ ~OccludedScene();
+
+ virtual void add(const Renderable &);
+ virtual void remove(const Renderable &);
+
+ virtual void render(Renderer &, const Tag &) const;
+};
+
+} // namespace GL
+} // namespace Msp
+
+#endif