Finally, proper instancing support, none of the slower and/or VRAM-wasting hacks.
The test scene had 1000 cubes. With the default mode, it used 100% of one core, and produced 140 fps. Using the new instancing, it used < 4% of one core, while outputting 360 fps.
Here's the test app:
Code: Select all
#include <irrlicht.h>
#define USE_INSTANCED 1
using namespace irr;
using namespace core;
using namespace scene;
using namespace video;
using namespace io;
static const char vtex[] =
"#extension GL_ARB_draw_instanced: enable\n"
"uniform sampler2D tex;"
"uniform mat4 viewp;"
"attribute vec4 color;"
"attribute mat4 model;"
"void main() {"
#if USE_INSTANCED
"gl_Position = viewp * model * gl_Vertex;"
"gl_FrontColor = color;"
#else
"gl_Position = ftransform();"
"gl_FrontColor = vec4(gl_MultiTexCoord0.xy, vec2(1.0));"
#endif
"}";
static const char ftex[] =
"void main() {"
"gl_FragColor = vec4(gl_Color);"
"}";
class scb_t: public IShaderConstantSetCallBack {
public:
virtual void OnSetConstants(IMaterialRendererServices* srv, s32) {
IVideoDriver* drv = srv->getVideoDriver();
matrix4 viewp = drv->getTransform(ETS_PROJECTION);
viewp *= drv->getTransform(ETS_VIEW);
srv->setVertexShaderConstant("viewp", viewp.pointer(), 16);
}
} scb;
int main() {
IrrlichtDevice *dev = createDevice(EDT_OPENGL);
if (!dev) return 1;
IVideoDriver *drv = dev->getVideoDriver();
ISceneManager* smgr = dev->getSceneManager();
IMesh *m = smgr->getGeometryCreator()->createCubeMesh(vector3df(1));
ICameraSceneNode *cam = smgr->addCameraSceneNodeFPS(0, 100, 0.03);
cam->setPosition(vector3df(0, 30, 0));
cam->setTarget(vector3df(0));
int shader = drv->getGPUProgrammingServices()->addHighLevelShaderMaterial(
vtex, ftex, &scb);
if (shader < 0) return 1;
SMaterial &cubemat = m->getMeshBuffer(0)->getMaterial();
cubemat.Lighting = false;
cubemat.MaterialType = (E_MATERIAL_TYPE) shader;
u32 i;
#if !USE_INSTANCED
for (i = 0; i < 1000; i++) {
smgr->addMeshSceneNode(m, 0, -1,
(vector3df(i / 100, (i / 10) % 10, i % 10) * 2) - 10);
}
#else
IInstancedMeshSceneNode *n = smgr->addInstancedMeshSceneNode(m);
for (i = 0; i < 1000; i++) {
n->addInstance((vector3df(i / 100, (i / 10) % 10, i % 10) * 2) - 10);
n->setInstanceColor(i, SColor(255, i/4, 0, 0));
}
#endif
int oldfps = 0;
while (dev->run()) {
drv->beginScene();
smgr->drawAll();
drv->endScene();
int fps = drv->getFPS();
if (fps != oldfps) {
wchar_t tmp[80];
swprintf(tmp, 80, L"fps %u, %u tris. Mode: %s", fps,
drv->getPrimitiveCountDrawn(),
USE_INSTANCED ? "instanced" : "normal");
dev->setWindowCaption(tmp);
oldfps = fps;
}
}
dev->drop();
return 0;
}
Please take a look at the user-facing API, and post any feedback:
Code: Select all
// Copyright (C) 2013 Lauri Kasanen
// This file is part of the "Irrlicht Engine".
// For conditions of distribution and use, see copyright notice in irrlicht.h
#ifndef __I_INSTANCED_MESH_SCENE_NODE_H_INCLUDED__
#define __I_INSTANCED_MESH_SCENE_NODE_H_INCLUDED__
#include "IMeshSceneNode.h"
namespace irr
{
namespace scene
{
//! Scene node to render many instances of a static mesh.
/** This scene node allows you to use geometry instancing on
supported hardware (dx9 cards and up). This means you can
render a crowd/forest/etc with just one draw call.
You will need to write a shader to use this node, instancing
is not supported using the fixed pipeline.
You may use the colors to send any custom per-instance data.
*/
class IInstancedMeshSceneNode : public IMeshSceneNode
{
public:
//! Constructor
IInstancedMeshSceneNode(ISceneNode* parent, ISceneManager* mgr, s32 id,
IMesh *mesh = NULL, u32 initialInstances = 0,
const core::vector3df& position = core::vector3df(0,0,0),
const core::vector3df& rotation = core::vector3df(0,0,0),
const core::vector3df& scale = core::vector3df(1.0f, 1.0f, 1.0f))
: IMeshSceneNode(parent, mgr, id, position, rotation, scale) {}
//! Destructor
virtual ~IInstancedMeshSceneNode() {}
//! Add a new instance.
virtual void addInstance(
const core::vector3df& position = core::vector3df(0,0,0),
const core::vector3df& rotation = core::vector3df(0,0,0),
const core::vector3df& scale = core::vector3df(1.0f, 1.0f, 1.0f),
const video::SColor& color = video::SColor(255, 255, 255, 255)) = 0;
//! Get the number of instances
virtual u32 getInstanceCount() const = 0;
//! Sets the position of this instance.
/** \param num: Number of the instance to edit.
\param pos: New position, relative to this scene node. */
virtual void setInstancePosition(u32 num, const core::vector3df& pos) = 0;
//! Sets the rotation of this instance.
/** \param num: Number of the instance to edit.
\param rot: New rotation, relative to this scene node. */
virtual void setInstanceRotation(u32 num, const core::vector3df& rot) = 0;
//! Sets the scale of this instance.
/** \param num: Number of the instance to edit.
\param scale: New scale, relative to this scene node. */
virtual void setInstanceScale(u32 num, const core::vector3df& scale) = 0;
//! Sets the color of this instance.
/** \param num: Number of the instance to edit.
\param col: New color. */
virtual void setInstanceColor(u32 num, const video::SColor& col) = 0;
//! Set the names of the vertex attributes
/** By default, the color is bound to "color", and the model/world
matrix of each instance is bound to "model". */
virtual void setAttributeNames(const char *color, const char *model) = 0;
};
} // end namespace scene
} // end namespace irr
#endif
The custom vertex attribute code (the last patch of the branch, "Custom VA - a bit hacky") is the only part that is not ready to be applied, as some better way for that should be found. But it works as is, so you can check out the branch if you want to test this feature.
The attrib code doesn't apply straight to trunk, and the GL extension wrappers also need manual applying. The user-facing API and the added count in draw calls should apply cleanly.