Initial support for instanced rendering
authorMikko Rasa <tdb@tdb.fi>
Wed, 19 Jun 2019 23:22:12 +0000 (02:22 +0300)
committerMikko Rasa <tdb@tdb.fi>
Fri, 21 Jun 2019 11:53:12 +0000 (14:53 +0300)
This is still a bit rough and does not support things like LoDs or custom
instance attributes.

13 files changed:
extensions/arb_draw_instanced.glext [new file with mode: 0644]
extensions/arb_instanced_arrays.glext [new file with mode: 0644]
shaderlib/msp_interface.glsl
source/batch.cpp
source/batch.h
source/instancearray.cpp [new file with mode: 0644]
source/instancearray.h [new file with mode: 0644]
source/mesh.cpp
source/mesh.h
source/renderer.cpp
source/renderer.h
source/vertexsetup.cpp
source/vertexsetup.h

diff --git a/extensions/arb_draw_instanced.glext b/extensions/arb_draw_instanced.glext
new file mode 100644 (file)
index 0000000..4e99f3b
--- /dev/null
@@ -0,0 +1 @@
+extension ARB_draw_instanced
diff --git a/extensions/arb_instanced_arrays.glext b/extensions/arb_instanced_arrays.glext
new file mode 100644 (file)
index 0000000..76e9ce1
--- /dev/null
@@ -0,0 +1 @@
+extension ARB_instanced_arrays
index fda84de95fff74de5ec6fc11180a05bc6a872168..5f391bb5fa882ca39897509974aba622c2b587c2 100644 (file)
@@ -73,6 +73,7 @@ layout(location=3) in vec4 color;
 layout(location=2) in vec3 normal;
 layout(location=4) in vec3 tangent;
 layout(location=5) in vec3 binormal;
+layout(location=12) in vec4 instance_transform[3];
 
 #pragma MSP stage(fragment)
 layout(location=0) out vec4 frag_color;
index a59fad99f03b988309ef60042324df71ebf3c6ab..af1d243e6cba1551e6be65255a55e69a4e7029cf 100644 (file)
@@ -1,3 +1,4 @@
+#include <msp/gl/extensions/arb_draw_instanced.h>
 #include <msp/gl/extensions/ext_draw_range_elements.h>
 #include <msp/gl/extensions/msp_legacy_features.h>
 #include <msp/gl/extensions/msp_primitive_restart.h>
@@ -235,6 +236,16 @@ void Batch::draw() const
                glDrawElements(prim_type, size(), data_type, data_ptr);
 }
 
+void Batch::draw_instanced(unsigned count) const
+{
+       static Require req(ARB_draw_instanced);
+
+       BindRestore _bind_ibuf(get_buffer(), ELEMENT_ARRAY_BUFFER);
+       const void *data_ptr = setup_draw();
+
+       glDrawElementsInstanced(prim_type, size(), data_type, data_ptr, count);
+}
+
 const void *Batch::setup_draw() const
 {
        if(restart)
index 7212383e01a2e973ac39185592f39c69f3ac76cc..82d8fbcada662440e5374ec8504cb3106c4104ee 100644 (file)
@@ -65,6 +65,7 @@ public:
        unsigned get_index(unsigned) const;
 
        void draw() const;
+       void draw_instanced(unsigned) const;
 private:
        const void *setup_draw() const;
        static void set_restart_index(unsigned);
diff --git a/source/instancearray.cpp b/source/instancearray.cpp
new file mode 100644 (file)
index 0000000..4156c6f
--- /dev/null
@@ -0,0 +1,124 @@
+#include <msp/core/algorithm.h>
+#include <msp/core/maputils.h>
+#include <msp/gl/extensions/arb_draw_instanced.h>
+#include <msp/gl/extensions/arb_instanced_arrays.h>
+#include <msp/gl/extensions/arb_vertex_array_object.h>
+#include <msp/gl/extensions/arb_vertex_shader.h>
+#include "buffer.h"
+#include "camera.h"
+#include "instancearray.h"
+#include "mesh.h"
+#include "object.h"
+#include "objectinstance.h"
+#include "renderer.h"
+#include "technique.h"
+#include "vertexsetup.h"
+
+using namespace std;
+
+namespace Msp {
+namespace GL {
+
+InstanceArray::InstanceArray(const Object &o):
+       object(o),
+       instance_data(0),
+       instance_buffer(0),
+       vtx_setup(0),
+       matrix_offset(0)
+{
+       if(ARB_vertex_array_object && ARB_instanced_arrays && ARB_draw_instanced)
+       {
+               instance_data = new VertexArray((ATTRIB4,12, ATTRIB4,13, ATTRIB4,14));
+               const VertexFormat &fmt = instance_data->get_format();
+               matrix_offset = fmt.offset(make_indexed_component(ATTRIB4, 12));
+
+               instance_buffer = new Buffer(ARRAY_BUFFER);
+               instance_data->use_buffer(instance_buffer);
+
+               vtx_setup = new VertexSetup;
+               vtx_setup->set_vertex_array(object.get_mesh()->get_vertices());
+               vtx_setup->set_index_buffer(*object.get_mesh()->get_index_buffer());
+               vtx_setup->set_instance_array(instance_data);
+       }
+       else
+               static Require req(ARB_vertex_shader);
+}
+
+InstanceArray::~InstanceArray()
+{
+       delete vtx_setup;
+       delete instance_data;
+       delete instance_buffer;
+}
+
+void InstanceArray::append(ObjectInstance *inst)
+{
+       instances.push_back(inst);
+       if(instance_data)
+       {
+               if(instance_data->size()<instances.size())
+                       instance_data->append();
+               update_instance_matrix(instances.size()-1);
+       }
+}
+
+void InstanceArray::remove(ObjectInstance &inst)
+{
+       vector<ObjectInstance *>::iterator i = find(instances, &inst);
+       if(i==instances.end())
+               throw key_error(&inst);
+
+       delete *i;
+       *i = instances.back();
+       instances.pop_back();
+}
+
+void InstanceArray::update_instance_matrix(unsigned index)
+{
+       if(!instance_data)
+               return;
+
+       const Matrix &m = *instances[index]->get_matrix();
+
+       float *d = instance_data->modify(instances.size()-1);
+       for(unsigned i=0; i<12; ++i)
+               d[matrix_offset+i] = m(i/4, i%4);
+}
+
+void InstanceArray::render(Renderer &renderer, const Tag &tag) const
+{
+       if(instances.empty())
+               return;
+
+       if(instance_data)
+       {
+               const Technique *tech = object.get_technique();
+               if(!tech)
+                       throw logic_error("no technique");
+               if(!tech->has_pass(tag))
+                       return;
+               const RenderPass &pass = tech->get_pass(tag);
+
+               const Mesh *mesh = object.get_mesh();
+               mesh->get_vertices().refresh();
+               instance_data->refresh();
+
+               Renderer::Push push(renderer);
+               pass.apply(renderer);
+               mesh->draw_instanced(renderer, *vtx_setup, instances.size());
+       }
+       else
+       {
+               for(vector<ObjectInstance *>::const_iterator i=instances.begin(); i!=instances.end(); ++i)
+               {
+                       const Matrix &m = *(*i)->get_matrix();
+                       glVertexAttrib4f(12, m(0, 0), m(0, 1), m(0, 2), m(0, 3));
+                       glVertexAttrib4f(13, m(1, 0), m(1, 1), m(1, 2), m(1, 3));
+                       glVertexAttrib4f(14, m(2, 0), m(2, 1), m(2, 2), m(2, 3));
+                       (*i)->render(renderer, tag);
+               }
+       }
+}
+
+} // namespace GL
+} // namespace Msp
diff --git a/source/instancearray.h b/source/instancearray.h
new file mode 100644 (file)
index 0000000..ffba39e
--- /dev/null
@@ -0,0 +1,82 @@
+#ifndef MSP_GL_INSTANCEARRAY_H_
+#define MSP_GL_INSTANCEARRAY_H_
+
+#include <vector>
+#include "programdata.h"
+#include "renderable.h"
+
+namespace Msp {
+namespace GL {
+
+class Buffer;
+class Object;
+class ObjectInstance;
+class VertexArray;
+class VertexSetup;
+
+/**
+Renders multiple instances of an Object in an efficient manner.  If instanced
+rendering is supported, only one draw call per Batch needs to be issued.
+
+Changing the Mesh of the Object while an InstanceArray exists is not supported.
+*/
+class InstanceArray: public Renderable
+{
+public:
+       template<typename T>
+       class Instance: public T
+       {
+       private:
+               InstanceArray &array;
+               unsigned index;
+
+       public:
+               Instance(const Object &o, InstanceArray &a, unsigned i): T(o), array(a), index(i) { }
+
+               virtual void set_matrix(const Matrix &);
+       };
+
+private:
+       const Object &object;
+       std::vector<ObjectInstance *> instances;
+       VertexArray *instance_data;
+       Buffer *instance_buffer;
+       VertexSetup *vtx_setup;
+       unsigned matrix_offset;
+
+public:
+       InstanceArray(const Object &);
+       ~InstanceArray();
+
+       void set_matrix_attribute(const std::string &);
+
+       template<typename T = ObjectInstance>
+       T &append();
+private:
+       void append(ObjectInstance *);
+       void update_instance_matrix(unsigned);
+public:
+       void remove(ObjectInstance &);
+
+       virtual void render(Renderer &, const Tag &) const;
+};
+
+template<typename T>
+T &InstanceArray::append()
+{
+       Instance<T> *inst = new Instance<T>(object, *this, instances.size());
+       append(inst);
+       return *inst;
+}
+
+template<typename T>
+void InstanceArray::Instance<T>::set_matrix(const Matrix &m)
+{
+       T::set_matrix(m);
+       array.update_instance_matrix(index);
+}
+
+} // namespace GL
+} // namespace Msp
+
+#endif
index a463ba7fb7c7a1414ee42ed116264d672730f514..5f9c60d03919e2fe262d4bf848325eaee30dd6e5 100644 (file)
@@ -175,6 +175,25 @@ void Mesh::draw(Renderer &renderer) const
                renderer.draw(*i);
 }
 
+void Mesh::draw_instanced(Renderer &renderer, const VertexSetup &vs, unsigned count) const
+{
+       if(vs.get_vertex_array()!=&vertices)
+               throw invalid_argument("Mesh::draw_instanced");
+
+       if(manager)
+       {
+               manager->resource_used(*this);
+               if(disallow_rendering)
+                       return;
+       }
+
+       renderer.set_vertex_setup(&vs);
+       renderer.set_winding_test(winding);
+
+       for(vector<Batch>::const_iterator i=batches.begin(); i!=batches.end(); ++i)
+               renderer.draw_instanced(*i, count);
+}
+
 void Mesh::bind() const
 {
        /* If VAOs are not supported, vtx_setup is zero and set_current won't get
index db0163e17ef3521ef471109758b9d846c6649782..1ee58a6227ea52427f93113bf9e631582f076467 100644 (file)
@@ -77,6 +77,7 @@ private:
 
 public:
        const VertexArray &get_vertices() const { return vertices; }
+       const VertexSetup *get_vertex_setup() const { return vtx_setup; }
        const Buffer *get_index_buffer() const { return ibuf; }
        unsigned get_n_vertices() const;
        float *modify_vertex(unsigned);
@@ -88,6 +89,7 @@ public:
 
        void draw() const;
        void draw(Renderer &) const;
+       void draw_instanced(Renderer &, const VertexSetup &, unsigned) const;
 
        /** Binds the mesh for rendering.  The vertex array is applied using generic
        attributes only.  Uses vertex array object if possible. */
index 53a3f45237463e5fffed3e084805326453e5fd3a..cc00ac8e3affda145ec8e2f8afcc5c1d7bb4af40 100644 (file)
@@ -272,6 +272,13 @@ void Renderer::draw(const Batch &batch)
        batch.draw();
 }
 
+void Renderer::draw_instanced(const Batch &batch, unsigned count)
+{
+       apply_state();
+
+       batch.draw_instanced(count);
+}
+
 void Renderer::apply_state()
 {
        /* We (mostly) let the objects themselves figure out if the binding has
index b2fcf4c4e6fba065980c7739ecf375a5a4bbda87..dbb2d4905cf57ab2e3c18ef76816af577851da3b 100644 (file)
@@ -172,6 +172,7 @@ public:
 
        void render(const Renderable &, const Tag & = Tag());
        void draw(const Batch &);
+       void draw_instanced(const Batch &, unsigned);
 
 private:
        void apply_state();
index 547dc0967584f690e7c29f4e82baf564c32d1838..8465ab060d54fe77041106a2c3c67a342102116e 100644 (file)
@@ -1,5 +1,6 @@
 #include <msp/core/raii.h>
 #include <msp/gl/extensions/arb_direct_state_access.h>
+#include <msp/gl/extensions/arb_instanced_arrays.h>
 #include <msp/gl/extensions/arb_vertex_array_object.h>
 #include <msp/gl/extensions/arb_vertex_attrib_binding.h>
 #include <msp/gl/extensions/arb_vertex_buffer_object.h>
@@ -14,7 +15,8 @@ namespace GL {
 
 VertexSetup::VertexSetup():
        dirty(0),
-       array(0),
+       vertex_array(0),
+       inst_array(0),
        index_buffer(0)
 {
        static Require req(ARB_vertex_array_object);
@@ -33,10 +35,19 @@ VertexSetup::~VertexSetup()
 
 void VertexSetup::set_vertex_array(const VertexArray &a)
 {
-       array = &a;
+       vertex_array = &a;
        update(VERTEX_ARRAY);
 }
 
+void VertexSetup::set_instance_array(const VertexArray *a)
+{
+       if(a)
+               static Require req(ARB_instanced_arrays);
+
+       inst_array = a;
+       update(INSTANCE_ARRAY);
+}
+
 void VertexSetup::set_index_buffer(const Buffer &ibuf)
 {
        index_buffer = &ibuf;
@@ -53,7 +64,13 @@ void VertexSetup::update(unsigned mask) const
        }
 
        if(mask&VERTEX_ARRAY)
-               update_vertex_array(direct);
+               update_vertex_array(*vertex_array, 0, 0, direct);
+
+       if(mask&INSTANCE_ARRAY)
+       {
+               if(inst_array)
+                       update_vertex_array(*inst_array, 1, 1, direct);
+       }
 
        if(mask&INDEX_BUFFER)
        {
@@ -64,14 +81,17 @@ void VertexSetup::update(unsigned mask) const
        }
 }
 
-void VertexSetup::update_vertex_array(bool direct) const
+void VertexSetup::update_vertex_array(const VertexArray &array, unsigned binding, unsigned divisor, bool direct) const
 {
-       Conditional<Bind> bind_vbuf(!direct, array->get_buffer(), ARRAY_BUFFER);
+       Conditional<Bind> bind_vbuf(!direct, array.get_buffer(), ARRAY_BUFFER);
 
-       const VertexFormat &fmt = array->get_format();
+       const VertexFormat &fmt = array.get_format();
        unsigned stride = get_stride(fmt)*sizeof(float);
        if(direct)
-               glVertexArrayVertexBuffer(id, 0, array->get_buffer()->get_id(), 0, stride);
+       {
+               glVertexArrayVertexBuffer(id, binding, array.get_buffer()->get_id(), 0, stride);
+               glVertexArrayBindingDivisor(id, binding, divisor);
+       }
 
        unsigned offset = 0;
        for(const unsigned char *c=fmt.begin(); c!=fmt.end(); ++c)
@@ -86,7 +106,7 @@ void VertexSetup::update_vertex_array(bool direct) const
                                glVertexArrayAttribFormat(id, t, 4, GL_UNSIGNED_BYTE, true, offset);
                        else
                                glVertexArrayAttribFormat(id, t, sz, GL_FLOAT, false, offset);
-                       glVertexArrayAttribBinding(id, t, 0);
+                       glVertexArrayAttribBinding(id, t, binding);
                        glEnableVertexArrayAttrib(id, t);
                }
                else
@@ -95,6 +115,7 @@ void VertexSetup::update_vertex_array(bool direct) const
                                glVertexAttribPointer(t, 4, GL_UNSIGNED_BYTE, true, stride, reinterpret_cast<unsigned char *>(offset));
                        else
                                glVertexAttribPointer(t, sz, GL_FLOAT, false, stride, reinterpret_cast<float *>(offset));
+                       glVertexAttribDivisor(t, divisor);
                        glEnableVertexAttribArray(t);
                }
                offset += sz*sizeof(float);
index 0155302cd6fb86bad0b99537e87a363ed0588f40..9e7409ac64b71f3574a32c778964089e21bdf3bf 100644 (file)
@@ -18,12 +18,14 @@ private:
        enum ComponentMask
        {
                VERTEX_ARRAY = 1,
-               INDEX_BUFFER = 2
+               INSTANCE_ARRAY = 2,
+               INDEX_BUFFER = 4
        };
 
        unsigned id;
        mutable unsigned dirty;
-       const VertexArray *array;
+       const VertexArray *vertex_array;
+       const VertexArray *inst_array;
        const Buffer *index_buffer;
 
 public:
@@ -31,13 +33,15 @@ public:
        ~VertexSetup();
 
        void set_vertex_array(const VertexArray &);
-       void set_instance_array(const VertexArray &);
+       void set_instance_array(const VertexArray *);
        void set_index_buffer(const Buffer &);
+       const VertexArray *get_vertex_array() const { return vertex_array; }
+       const VertexArray *get_instance_array() const { return inst_array; }
        const Buffer *get_index_buffer() const { return index_buffer; }
 
 private:
        void update(unsigned) const;
-       void update_vertex_array(bool) const;
+       void update_vertex_array(const VertexArray &, unsigned, unsigned, bool) const;
 
 public:
        void bind() const;