framerate issues

You are an experienced programmer and have a problem with the engine, shaders, or advanced effects? Here you'll get answers.
No questions about C++ programming or topics which are answered in the tutorials!
Seven
Posts: 1034
Joined: Mon Nov 14, 2005 2:03 pm

framerate issues

Post by Seven »

I am trying to understand the frame rate disconnect that I have in a game and created a small program to duplicate what I am seeing. At each step in this process, I wait for the frame rate to stabilize before moving to the next step.
Any help would be appreciated!

4200 FPS program launch (no nodes)
7 FPS after add nodes
2100 FPS after hide nodes (both opengl and directx drivers)
9 FPS after show nodes
3700 FPS after remove nodes (OpenGL driver does not return to program launch FLS rate?)
4200 FPS after remove nodes (DirectX driver acts as I would expect )

Can anyone explain to me a few items :
1) why does hiding the nodes drop the FPS so much from program start? ( it should only be a run through the list determine if each node is invisible and not process it, or is the list run though that slow?)
2) why does removing all of the nodes not return me to roughly the starting FPS?

Code: Select all

#include <irrlicht.h>
#include "driverChoice.h"
#include "exampleHelper.h"

using namespace irr;

irr::core::array<irr::scene::ISceneNode*> nodes;

irr::scene::IAnimatedMeshSceneNode* createAnimatedMesh(IrrlichtDevice* device, irr::core::stringc actorfilename, irr::core::vector3df pos, irr::core::vector3df rot, irr::core::vector3df scale)
{
	irr::scene::IAnimatedMeshSceneNode* node = 0;
	scene::IAnimatedMesh* mesh = device->getSceneManager()->getMesh(actorfilename);
	if (mesh)
	{
		node = device->getSceneManager()->addAnimatedMeshSceneNode(mesh, 0, 1, pos, rot, scale);
		if (node)
		{
			node->setMaterialFlag(video::EMF_LIGHTING, false);
			node->setMaterialFlag(video::EMF_FOG_ENABLE, false);
			return node;
		}
	}
	return node;
}

void toggleVisibility()
{
	printf("toggling visibility\n");
	for (irr::s32 x = 0; x < nodes.size(); x++)
		nodes[x]->setVisible(!nodes[x]->isVisible());
}

void buildNodes(IrrlichtDevice* device)
{
	printf("building nodes\n");
	irr::core::vector3df pos(0, 0, 0);
	irr::core::vector3df rot(0, 0, 0);
	irr::core::vector3df scale(2, 2, 2);
	float offset = 200;
	for (int x = 0; x < 50; x++)
	{
		for (int z = 0; z < 50; z++)
		{
			pos.Z += offset;
			nodes.push_back(createAnimatedMesh(device, "media/_Assets/_Models/_Characters/RoyalKnight/RoyalKnight.b3d", pos, rot, scale));
		}
		pos.Z = 0;
		pos.X += offset;
	}
}

void destroyNodes()
{
	printf("destroying\n");
	for (irr::s32 x = 0; x < nodes.size(); x++) nodes[x]->remove();
	nodes.clear();
}

class MyEventReceiver : public IEventReceiver
{
private:
	IrrlichtDevice* device;
public:
	MyEventReceiver(IrrlichtDevice* dev):device(dev)
	{
	}

	virtual bool OnEvent(const SEvent& event)
	{
		switch (event.EventType)
		{
		case EEVENT_TYPE::EET_MOUSE_INPUT_EVENT:
			if (event.MouseInput.Event == EMIE_RMOUSE_PRESSED_DOWN)
				device->getSceneManager()->getActiveCamera()->setInputReceiverEnabled(!device->getSceneManager()->getActiveCamera()->isInputReceiverEnabled());
			break;
		case EEVENT_TYPE::EET_KEY_INPUT_EVENT:
			switch (event.KeyInput.Key)
			{
			case KEY_SPACE: if (event.KeyInput.PressedDown) toggleVisibility(); break;
			case KEY_KEY_B: if (event.KeyInput.PressedDown) buildNodes(device); break;
			case KEY_KEY_N: if (event.KeyInput.PressedDown) destroyNodes(); break;
			}
		}
		return false;
	}
};


/*
The start of the main function starts like in most other example. We ask the
user for the desired renderer and start it up. This time with the advanced
parameter handling.
*/
int main()
{
	// ask user for driver
	video::E_DRIVER_TYPE driverType = driverChoiceConsole();
	if (driverType == video::EDT_COUNT)	return 1;

	// create device with full flexibility over creation parameters
	// you can add more parameters if desired, check irr::SIrrlichtCreationParameters
	irr::SIrrlichtCreationParameters params;
	params.DriverType = driverType;
	params.WindowSize = core::dimension2d<u32>(640, 480);
	IrrlichtDevice* device = createDeviceEx(params);

	if (device == 0) return 1; // could not create selected driver.

	video::IVideoDriver* driver = device->getVideoDriver();
	scene::ISceneManager* smgr = device->getSceneManager();
	gui::IGUIEnvironment* env = device->getGUIEnvironment();

	driver->setTextureCreationFlag(video::ETCF_ALWAYS_32_BIT, true);

	const io::path mediaPath = getExampleMediaPath();

	// add irrlicht logo
	env->addImage(driver->getTexture(mediaPath + "irrlichtlogo3.png"), core::position2d<s32>(10, 10));

	// add camera
	scene::ICameraSceneNode* camera = smgr->addCameraSceneNodeFPS(0, 100.0f, 1.2f);
		camera->setPosition(core::vector3df(2700 * 2, 255 * 2, 2600 * 2));
		camera->setTarget(core::vector3df(2397 * 2, 343 , 2700 * 2));
		camera->setFarValue(42000.0f);
		camera->setInputReceiverEnabled(false);

	// disable mouse cursor
	device->getCursorControl()->setVisible(true);

	// create event receiver
	MyEventReceiver receiver(device);
	device->setEventReceiver(&receiver);

	int lastFPS = -1;

	while (device->run())
		if (device->isWindowActive())
		{
			driver->beginScene(video::ECBF_COLOR | video::ECBF_DEPTH, video::SColor(0));

			smgr->drawAll();
			env->drawAll();

			driver->endScene();

			// display frames per second in window title
			int fps = driver->getFPS();
			if (lastFPS != fps)
			{
				core::stringw str = L"Terrain Renderer - Irrlicht Engine [";
				str += driver->getName();
				str += "] FPS:";
				str += fps;
				device->setWindowCaption(str.c_str());
				lastFPS = fps;
			}
		}

	device->drop();

	return 0;
}

Seven
Posts: 1034
Joined: Mon Nov 14, 2005 2:03 pm

Re: framerate issues

Post by Seven »

EPID_SM_RENDER_GUI_NODES needs to be added to EProfileIDs.h
CuteAlien
Admin
Posts: 9734
Joined: Mon Mar 06, 2006 2:25 pm
Location: Tübingen, Germany
Contact:

Re: framerate issues

Post by CuteAlien »

Yeah, sorry about EPID_SM_RENDER_GUI_NODES , I also noticed it earlier and already added it ;-)

As for hidden nodes costing time - pretty much as you guessed. It goes 2 times over all elements to check if they are visible (once for animation once for rendering). Which takes around 0.2ms here. Which is nearly the difference between 2100 and 4200 FPS. But also means with 100 fps you can call drawAll around 50 times for your 2500 nodes until you lose 1 frame.

I can reproduce the OpenGL thing. Seems to be endScene(), but even when disabling hardwardbuffer and occlusion stuff it stays. There doesn't seem to be any additional drawing going on. Interestingly it doesn't happen if you draw stuff only once - I had to call drawAll around 5-10 times after loading your nodes before that effect started (not always same number). So I could only guess... like maybe internal driver stuff.
IRC: #irrlicht on irc.libera.chat
Code snippet repository: https://github.com/mzeilfelder/irr-playground-micha
Free racer made with Irrlicht: http://www.irrgheist.com/hcraftsource.htm
Seven
Posts: 1034
Joined: Mon Nov 14, 2005 2:03 pm

Re: framerate issues

Post by Seven »

no worries and thanks for taking a look. You are doing a heck of a job on this engine btw. I just wanted to see if my thinking was right.
I modified the IProfiler a bit to show current pass time instead of the sum of times to see it a little better. (just changed the += diff to = diff)

also I found that it doesn't matter how many nodes there are (even with only 2 nodes I see the same drop when all are invisible). it appears that just accessing the lists has that price regardless of the size. Apparently just a rabbit hole I fell into. I sometimes do that :)

anyhow, thanks again for checking my sanity.
I currently have my world running at 400fps (terrain with full foliage(grass, flowers, trees, rocks, etc)
physX running on the GPU and main character wandering aimlessly around.
I am trying to improve on that frame rate before I add to much AI to the system since I am afraid my home brewed AI system is fairly slow. In order to improve, I set up an LOD system that sets the visibility of the scenenodes based on the distance from the camera / player. This seems to work very nicely however, that's when I noticed the situation above.
CuteAlien
Admin
Posts: 9734
Joined: Mon Mar 06, 2006 2:25 pm
Location: Tübingen, Germany
Contact:

Re: framerate issues

Post by CuteAlien »

The trick with the profiler is to reset the values once in a while. Thought I should add a minimal time as well, as that is basically the "real time" (everything that adds to the minimal time is usually something outside of your control).
IRC: #irrlicht on irc.libera.chat
Code snippet repository: https://github.com/mzeilfelder/irr-playground-micha
Free racer made with Irrlicht: http://www.irrgheist.com/hcraftsource.htm
Seven
Posts: 1034
Joined: Mon Nov 14, 2005 2:03 pm

Re: framerate issues

Post by Seven »

A little theoretical help please...

I have been toying with my vegetation code and, although I am pleased with my results so far, I believe that I can get better frame rates maybe through the use of a different philosophy. Anyhow, below I will post a test project that I am using to see the differences in speed when using different methods.

all methods use a pre-computed global array of vector3df's as positions for where grass should be located. (gPos)

// trying to think outside the box on this one
1) I created a new meshSceneNode (IGE_SceneNode_Grass) that holds a mesh and accepts an array of vector3df's called Positions.
Each frame, I calculate which grass node positions in gPos are in view and add them to the Positions list.
During the render call the IGE_SceneNode_Grass then loops though the positions array and performs a drawMeshBuffer() repeatedly using the single stored mesh.

// basically a brute force method of hiding / showing nodes as needed
2) I create a massive array of scenenodes (gNodes)
each frame, for each node I calculate if it is in view and set it to visible / not visible as needed

// semi creative method
3) I create a massive array of scenenodes and set all to nullptr (gNodes)
each frame, for each gPos I calculate if it is in view and
if it is, I check if a scenenode already exists in gNodes (if not then create one)
if it is not then remove any existing node in the gNodes array


the end result is that 3rd method is the fastest and is the one I am using in my game, however, I think that option 1 could be faster? if I could prevent the render call from setting up the pipeline each drawmeshbuffer() call. I think will have to modify Irrlicht to do that though. I am thinking something like
adding a drawMeshBufferRepeat() which wont setup the pipeline as much but rather leave it as it is and use it and then draw the mesh repeatedly using only a new transform.

Any thoughts on how to improve the IGE_SceneNode_Grass or am I just compeltely barking up the wrong tree with this?

*** note : the only truly pertinent part of the IGE_SceneNode_Grass class is the render() function......
Last edited by Seven on Fri Jul 23, 2021 4:48 pm, edited 1 time in total.
Seven
Posts: 1034
Joined: Mon Nov 14, 2005 2:03 pm

Re: framerate issues

Post by Seven »

Code: Select all

#pragma once

#include "Irrlicht.h"
using namespace irr;
using namespace video;
using namespace core;
using namespace gui;
using namespace scene;
using namespace io;

namespace IGE
{
	class IGE_SceneNode_Grass : public IMeshSceneNode
	{
		public:

			//! constructor
			IGE_SceneNode_Grass(array<vector3df>& Pos, IMesh* mesh, ISceneNode* parent, ISceneManager* mgr, s32 id,
				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));

			//! destructor
			virtual ~IGE_SceneNode_Grass();

			//! frame
			virtual void OnRegisterSceneNode() _IRR_OVERRIDE_;

			//! renders the node.
			virtual void render() _IRR_OVERRIDE_;

			//! returns the axis aligned bounding box of this node
			virtual const core::aabbox3d<f32>& getBoundingBox() const _IRR_OVERRIDE_;

			//! returns the material based on the zero based index i. To get the amount
			//! of materials used by this scene node, use getMaterialCount().
			//! This function is needed for inserting the node into the scene hierarchy on a
			//! optimal position for minimizing renderstate changes, but can also be used
			//! to directly modify the material of a scene node.
			virtual video::SMaterial& getMaterial(u32 i) _IRR_OVERRIDE_;

			//! returns amount of materials used by this scene node.
			virtual u32 getMaterialCount() const _IRR_OVERRIDE_;

			//! Writes attributes of the scene node.
			virtual void serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options = 0) const _IRR_OVERRIDE_;

			//! Reads attributes of the scene node.
			virtual void deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options = 0) _IRR_OVERRIDE_;

			//! Returns type of the scene node
			virtual ESCENE_NODE_TYPE getType() const _IRR_OVERRIDE_ { return ESNT_MESH; }

			//! Sets a new mesh
			virtual void setMesh(IMesh* mesh) _IRR_OVERRIDE_;

			//! Returns the current mesh
			virtual IMesh* getMesh(void) _IRR_OVERRIDE_ { return Mesh; }

			//! Sets if the scene node should not copy the materials of the mesh but use them in a read only style.
			/* In this way it is possible to change the materials a mesh causing all mesh scene nodes
			referencing this mesh to change too. */
			virtual void setReadOnlyMaterials(bool readonly) _IRR_OVERRIDE_;

			//! Returns if the scene node should not copy the materials of the mesh but use them in a read only style
			virtual bool isReadOnlyMaterials() const _IRR_OVERRIDE_;

			//! Creates a clone of this scene node and its children.
			//virtual ISceneNode* clone(ISceneNode* newParent = 0, ISceneManager* newManager = 0) _IRR_OVERRIDE_;

			//! Creates shadow volume scene node as child of this node
			//! and returns a pointer to it.
			virtual IShadowVolumeSceneNode* addShadowVolumeSceneNode(const IMesh* shadowMesh,
				s32 id, bool zfailmethod = true, f32 infinity = 10000.0f) _IRR_OVERRIDE_ {
				return nullptr;
			}

	protected:

			void copyMaterials();

			core::array<video::SMaterial> Materials;
			core::aabbox3d<f32> Box;
			video::SMaterial ReadOnlyMaterial;

			IMesh* Mesh;
			array<vector3df> &Positions;

			s32 PassCount;
			bool ReadOnlyMaterials;
	};

} // end namespace IGE


Seven
Posts: 1034
Joined: Mon Nov 14, 2005 2:03 pm

Re: framerate issues

Post by Seven »

Code: Select all


#include "IGE_SceneNode_Grass.h"

namespace IGE
{
	//! constructor
	IGE_SceneNode_Grass::IGE_SceneNode_Grass(array<vector3df>& Pos, IMesh* mesh, ISceneNode* parent, ISceneManager* mgr, s32 id,
		const core::vector3df& position, const core::vector3df& rotation,
		const core::vector3df& scale)
		: IMeshSceneNode(parent, mgr, id, position, rotation, scale), Mesh(0), PassCount(0), ReadOnlyMaterials(false),Positions(Pos)
	{
#ifdef _DEBUG
		setDebugName("IGE_SceneNode_Grass");
#endif
		setMesh(mesh);
		Box = aabbox3df(-100000,-100000,-100000,100000,100000,100000);
	}


	//! destructor
	IGE_SceneNode_Grass::~IGE_SceneNode_Grass()
	{
		if (Mesh) Mesh->drop();
	}


	//! frame
	void IGE_SceneNode_Grass::OnRegisterSceneNode()
	{
		if (IsVisible && Mesh)
		{
			SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT);

			ISceneNode::OnRegisterSceneNode();
		}
	}


	//! renders the node.
	void IGE_SceneNode_Grass::render()
	{
		video::IVideoDriver* driver = SceneManager->getVideoDriver();

		if (!Mesh || !driver || !IsVisible) return;

		video::SMaterial mat;
		scene::IMeshBuffer* mb = Mesh->getMeshBuffer(0);
		const video::SMaterial& material = ReadOnlyMaterials ? mb->getMaterial() : Materials[0];
		const bool transparent = driver->needsTransparentRenderPass(material);
		driver->setMaterial(material);

		if (SceneManager->getSceneNodeRenderPass() == scene::ESNRP_TRANSPARENT)
		{
			for (u32 index = 0; index < Positions.size(); index++)
			{
				AbsoluteTransformation.setTranslation(Positions[index]);
				driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
				driver->drawMeshBuffer(mb);
			}
		}
	}

	//! returns the axis aligned bounding box of this node
	const core::aabbox3d<f32>& IGE_SceneNode_Grass::getBoundingBox() const
	{
		return Box;
	}


	//! returns the material based on the zero based index i. To get the amount
	//! of materials used by this scene node, use getMaterialCount().
	//! This function is needed for inserting the node into the scene hierarchy on a
	//! optimal position for minimizing renderstate changes, but can also be used
	//! to directly modify the material of a scene node.
	video::SMaterial& IGE_SceneNode_Grass::getMaterial(u32 i)
	{
		if (Mesh && ReadOnlyMaterials && i < Mesh->getMeshBufferCount())
		{
			ReadOnlyMaterial = Mesh->getMeshBuffer(i)->getMaterial();
			return ReadOnlyMaterial;
		}

		if (i >= Materials.size())
			return ISceneNode::getMaterial(i);

		return Materials[i];
	}


	//! returns amount of materials used by this scene node.
	u32 IGE_SceneNode_Grass::getMaterialCount() const
	{
		if (Mesh && ReadOnlyMaterials)
			return Mesh->getMeshBufferCount();

		return Materials.size();
	}


	//! Sets a new mesh
	void IGE_SceneNode_Grass::setMesh(IMesh* mesh)
	{
		if (mesh)
		{
			mesh->grab();
			if (Mesh)
				Mesh->drop();

			Mesh = mesh;
			copyMaterials();
		}
	}

	void IGE_SceneNode_Grass::copyMaterials()
	{
		Materials.clear();

		if (Mesh)
		{
			video::SMaterial mat;

			for (u32 i = 0; i < Mesh->getMeshBufferCount(); ++i)
			{
				IMeshBuffer* mb = Mesh->getMeshBuffer(i);
				if (mb)
					mat = mb->getMaterial();

				Materials.push_back(mat);
			}
		}
	}


	//! Writes attributes of the scene node.
	void IGE_SceneNode_Grass::serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options) const
	{
		IMeshSceneNode::serializeAttributes(out, options);

		if (options && (options->Flags & io::EARWF_USE_RELATIVE_PATHS) && options->Filename)
		{
			const io::path path = SceneManager->getFileSystem()->getRelativeFilename(
				SceneManager->getFileSystem()->getAbsolutePath(SceneManager->getMeshCache()->getMeshName(Mesh).getPath()),
				options->Filename);
			out->addString("Mesh", path.c_str());
		}
		else
			out->addString("Mesh", SceneManager->getMeshCache()->getMeshName(Mesh).getPath().c_str());
		out->addBool("ReadOnlyMaterials", ReadOnlyMaterials);
	}


	//! Reads attributes of the scene node.
	void IGE_SceneNode_Grass::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options)
	{
		io::path oldMeshStr = SceneManager->getMeshCache()->getMeshName(Mesh);
		io::path newMeshStr = in->getAttributeAsString("Mesh");
		ReadOnlyMaterials = in->getAttributeAsBool("ReadOnlyMaterials");

		if (newMeshStr != "" && oldMeshStr != newMeshStr)
		{
			IMesh* newMesh = 0;
			IAnimatedMesh* newAnimatedMesh = SceneManager->getMesh(newMeshStr.c_str());

			if (newAnimatedMesh)
				newMesh = newAnimatedMesh->getMesh(0);

			if (newMesh)
				setMesh(newMesh);
		}

		// optional attribute to assign the hint to the whole mesh
		if (in->existsAttribute("HardwareMappingHint") &&
			in->existsAttribute("HardwareMappingBufferType"))
		{
			scene::E_HARDWARE_MAPPING mapping = scene::EHM_NEVER;
			scene::E_BUFFER_TYPE bufferType = scene::EBT_NONE;

			core::stringc smapping = in->getAttributeAsString("HardwareMappingHint");
			if (smapping.equals_ignore_case("static"))
				mapping = scene::EHM_STATIC;
			else if (smapping.equals_ignore_case("dynamic"))
				mapping = scene::EHM_DYNAMIC;
			else if (smapping.equals_ignore_case("stream"))
				mapping = scene::EHM_STREAM;

			core::stringc sbufferType = in->getAttributeAsString("HardwareMappingBufferType");
			if (sbufferType.equals_ignore_case("vertex"))
				bufferType = scene::EBT_VERTEX;
			else if (sbufferType.equals_ignore_case("index"))
				bufferType = scene::EBT_INDEX;
			else if (sbufferType.equals_ignore_case("vertexindex"))
				bufferType = scene::EBT_VERTEX_AND_INDEX;

			IMesh* mesh = getMesh();
			if (mesh)
				mesh->setHardwareMappingHint(mapping, bufferType);
		}

		IMeshSceneNode::deserializeAttributes(in, options);
	}


	//! Sets if the scene node should not copy the materials of the mesh but use them in a read only style.
	/* In this way it is possible to change the materials a mesh causing all mesh scene nodes
	referencing this mesh to change too. */
	void IGE_SceneNode_Grass::setReadOnlyMaterials(bool readonly)
	{
		ReadOnlyMaterials = readonly;
	}


	//! Returns if the scene node should not copy the materials of the mesh but use them in a read only style
	bool IGE_SceneNode_Grass::isReadOnlyMaterials() const
	{
		return ReadOnlyMaterials;
	}


/*
	//! Creates a clone of this scene node and its children.
	ISceneNode* IGE_SceneNode_Grass::clone(ISceneNode* newParent, ISceneManager* newManager)
	{
		if (!newParent)
			newParent = Parent;
		if (!newManager)
			newManager = SceneManager;

		IGE_SceneNode_Grass* nb = new IGE_SceneNode_Grass(Positions, Mesh, newParent, newManager, ID, RelativeTranslation, RelativeRotation, RelativeScale);

		nb->cloneMembers(this, newManager);
		nb->ReadOnlyMaterials = ReadOnlyMaterials;
		nb->Materials = Materials;
		if (newParent) nb->drop();
		return nb;
	}
*/
} // end namspace IGE

Seven
Posts: 1034
Joined: Mon Nov 14, 2005 2:03 pm

Re: framerate issues

Post by Seven »

Code: Select all

#include <irrlicht.h>
#include "driverChoice.h"
#include "exampleHelper.h"
#include "IGE_SceneNode_Grass.h"

using namespace irr;
using namespace IGE;

/*
*	this demo demonstrates two different methods for rendering grass
*	if using the MODE_NEWNODE method, 
*		create a single node and pass the Positions array to it
*		and it will render the same mesh repeatedly in whatever positions the Positions array holds.
*	if using the MODE_HIDESHOW method
*		create all of the meshscenenodes and hold them in the gNodes array for easy access
*		and it will render all of the visible nodes
*	if using the MODE_CREATENEW method
*		each tick, if the gPos is in range then we should have a node
*		if we already have  a node then do nothing else add a new node
*		if the gPos is not in view then delete th node if it exists
*	regardless of which method is used, each tick run through the gPos multiarray 
*		if the position is close enough to the camera AND is inside the camera's frustrum
*			then do whatever the method desires
*
*/

#define MODE_NEWNODE	0
#define MODE_HIDESHOW	1
#define MODE_CREATENEW	2

// toggle which method to use
int GRASSMODE = MODE_NEWNODE;

// globals are easier for simple demos
IrrlichtDevice*			device = 0;
video::IVideoDriver*	driver = 0;
scene::ISceneManager*	smgr = 0;
gui::IGUIEnvironment*	env = 0;

// a few global variables specific to this demo 
IGE_SceneNode_Grass*		grass = 0;			// the grass node
IMeshSceneNode*				Plane = 0;			// the plane
scene::ICameraSceneNode*	camera = 0;			// the main FPS camera
scene::ICameraSceneNode*	camera2 = 0;		// a test camera to check grass rendering through
scene::ICameraSceneNode*	viewfromcamera = 0;	// which camera to check grass rendering through	
array<vector3df>			Positions;			// array of points to render grass meshes at

#define VIEW_DISTANCE		4000
#define VIEW_DISTANCESQ		VIEW_DISTANCE * VIEW_DISTANCE
#define XSCALE				30000
#define ZSCALE				30000
#define GRASS_DENSITY		150
#define GRASSNODESCALE		vector3df(4,3,4) 
vector3df PlaneScale(XSCALE, 1, ZSCALE);

#define				NUM_COLUMNS XSCALE / GRASS_DENSITY
#define				NUM_ROWS ZSCALE / GRASS_DENSITY

vector3df			gPos[NUM_COLUMNS][NUM_ROWS];
ISceneNode*			gNodes[NUM_COLUMNS][NUM_ROWS];

// check if a point is inside the viewfromcamera view frustrum (based on whichever camera we are calculating with)
bool canbeseenbycampyramidplanes(const vector3df& pos)
{
	SViewFrustum frust = *viewfromcamera->getViewFrustum();
	bool pointinfrustrum = true;
	for (s32 i = 0; i < scene::SViewFrustum::VF_PLANE_COUNT; ++i)
	{
		if (frust.planes[i].classifyPointRelation(pos) == core::ISREL3D_FRONT) pointinfrustrum = false;
	}
	return pointinfrustrum;
}

// destroy all grass nodes
void destroyGrassNodes()
{
	if (grass) grass->remove(); grass = 0;

	for (int x = 0; x < NUM_COLUMNS; x++)
	{
		for (int z = 0; z < NUM_ROWS; z++)
		{
			if (gNodes[x][z]) gNodes[x][z]->remove();
			gNodes[x][z] = nullptr;
		}
	}
}

// create all grass nodes based on whether USE_NEW_NODE is true or not
void createGrassNodes()
{
	// start over
	destroyGrassNodes();

	// create the one mesh we are using
	IMesh* mesh = smgr->getMesh(("media/_assets/_models/_foliage/tropical/arteria3d_tropicalpack/misc/trop_grass(region).b3d"));

	if (GRASSMODE == MODE_NEWNODE)
	{
		// create the new node for use when USE_NEW_NODE is true
		grass = new IGE_SceneNode_Grass(Positions, mesh, smgr->getRootSceneNode(), smgr, 0, vector3df(), vector3df(), GRASSNODESCALE);
			grass->setMaterialFlag(video::EMF_NORMALIZE_NORMALS, true);
			grass->setMaterialType(E_MATERIAL_TYPE::EMT_TRANSPARENT_ALPHA_CHANNEL);
			grass->setMaterialFlag(video::EMF_LIGHTING, false);
			grass->setMaterialFlag(video::EMF_FOG_ENABLE, false);
	}

	if (GRASSMODE == MODE_HIDESHOW)
	{
		// create all of the other nodes for use when USE_NEW_NODE is false
		for (int x = 0; x < NUM_COLUMNS; x++)
		{
			for (int z = 0; z < NUM_ROWS; z++)
			{
				// add the grass node
				gNodes[x][z] = smgr->addMeshSceneNode(mesh, smgr->getRootSceneNode(), 0, gPos[x][z], vector3df(), GRASSNODESCALE);
				gNodes[x][z]->setMaterialFlag(video::EMF_NORMALIZE_NORMALS, true);
				gNodes[x][z]->setMaterialType(E_MATERIAL_TYPE::EMT_TRANSPARENT_ALPHA_CHANNEL);
				gNodes[x][z]->setMaterialFlag(video::EMF_LIGHTING, false);
				gNodes[x][z]->setMaterialFlag(video::EMF_FOG_ENABLE, false);
			}
		}
	}

	if (GRASSMODE == MODE_CREATENEW)
	{
		// do nothing because the update function will create nodes as needed
	}
}

// called each tick to determine what points are added to the grass node for rendering
// or which nodes are visible / invisible for the other method 
void updateGrass()
{
	// simple check to make sure our node is valid
	if (GRASSMODE == MODE_NEWNODE)
	{
		// clear all of the positions so far
		Positions.clear();

		// bail if we dont have a grass node
		if (!grass) return;
	}


	// the current camera position
	vector3df camPos = viewfromcamera->getPosition();

	// temporary variable
	vector3df p;

	IMesh* mesh = smgr->getMesh(("media/_assets/_models/_foliage/tropical/arteria3d_tropicalpack/misc/trop_grass(region).b3d"));

	// run through all the positions
	for (int x = 0; x < NUM_COLUMNS; x++)
	{
		for (int z = 0; z < NUM_ROWS; z++)
		{
			// the current position we are working with
			p = gPos[x][z];

			if (GRASSMODE == MODE_NEWNODE)
			{
				// if the point is close enough to the camera and is within the camera frustrum then add it to the grassnode
				if ((p.getDistanceFromSQ(camPos) < VIEW_DISTANCESQ) && (canbeseenbycampyramidplanes(p))) Positions.push_back(p);
			}

			if (GRASSMODE == MODE_HIDESHOW)
			{
				// if the point is close enough to the camera and is within the camera frustrum then make it visible, otherwise make it invisible
				if ((p.getDistanceFromSQ(camPos) < VIEW_DISTANCESQ) && (canbeseenbycampyramidplanes(p)))
				{
					gNodes[x][z]->setVisible(true);
				}
				else
				{
					gNodes[x][z]->setVisible(false);
				}
			}

			if (GRASSMODE == MODE_CREATENEW)
			{
				// if the point is close enough to the camera and is within the camera frustrum then 
				if ((p.getDistanceFromSQ(camPos) < VIEW_DISTANCESQ) && (canbeseenbycampyramidplanes(p)))
				{
					// if the node is null then add a new node node
					if (gNodes[x][z] == nullptr)
					{
						// add the grass node
						gNodes[x][z] = smgr->addMeshSceneNode(mesh, smgr->getRootSceneNode(), 0, gPos[x][z], vector3df(), GRASSNODESCALE);
						gNodes[x][z]->setMaterialFlag(video::EMF_NORMALIZE_NORMALS, true);
						gNodes[x][z]->setMaterialType(E_MATERIAL_TYPE::EMT_TRANSPARENT_ALPHA_CHANNEL);
						gNodes[x][z]->setMaterialFlag(video::EMF_LIGHTING, false);
						gNodes[x][z]->setMaterialFlag(video::EMF_FOG_ENABLE, false);
					}
				}
				else
				{
					// otherwise delete the node that is that spot
					if (gNodes[x][z] != nullptr)
					{
						gNodes[x][z]->remove();
						gNodes[x][z] = nullptr;
					}
				}
			}
		}
	}
}


/*
*  array to hold the positions of where nodes should be
*	simply set a vlue in each array entry
*/
void initGPos()
{
	// rememeber the plane's position so we can reset the loop variables (plane is actually rendered starting at position - scale/2
	vector3df planePos = Plane->getAbsolutePosition() - vector3df(PlaneScale / 2);

	// the point to add to the grass node (i move it up slightly from the plane Y position)
	vector3df pos = planePos;
	pos.Y += 2;

	// starting at the plane's visual position, run the length and width of the plane 
	for (int x = 0; x < NUM_COLUMNS; x++)
	{
		for (int z = 0; z < NUM_ROWS; z++)
		{
			// increment the grass mesh render offset
			pos.Z += GRASS_DENSITY;

			// store the position in the gPos multi array
			gPos[x][z] = pos;
		}
		pos.Z = planePos.Z;
		pos.X += GRASS_DENSITY;
	}

	// make sure we have an empty array for the nodes
	for (int x = 0; x < NUM_COLUMNS; x++)
	{
		for (int z = 0; z < NUM_ROWS; z++)
		{
			gNodes[x][z] = nullptr;
		}
	}
}

// create everything specific to this demo
void createScene()
{
	// add a test camera with a cube to show us where it is at
	camera2 = smgr->addCameraSceneNode();
		camera2->setDebugDataVisible(E_DEBUG_SCENE_TYPE::EDS_BBOX_ALL);
		ISceneNode* cube = smgr->addCubeSceneNode(20, camera2, 0);
			cube->setMaterialTexture(0, driver->getTexture("media/fire.bmp"));
			cube->setMaterialFlag(video::EMF_LIGHTING, false);
			cube->setMaterialFlag(video::EMF_FOG_ENABLE, false);

	// add the primary user controlled camera
	camera = smgr->addCameraSceneNodeFPS(0, 100.0f, 1.2f);
		camera->setPosition(core::vector3df(0, 500, 500));
		camera->setTarget(core::vector3df(0, 0, 0));
		camera->setFarValue(42000.0f);
		camera->setInputReceiverEnabled(false);

	// start the demo using the user controlled camera
	viewfromcamera = camera;

	// enable the  mouse cursor
	device->getCursorControl()->setVisible(true);

	// add a plane (just visual for the demo)
	IMesh* mesh = smgr->getGeometryCreator()->createCubeMesh(vector3df(1,1,1));
	Plane = smgr->addMeshSceneNode(mesh, smgr->getRootSceneNode(), 0, vector3df(-1000, -300, -1000), vector3df(), PlaneScale);
	Plane->setMaterialTexture(0, driver->getTexture("media/fire.bmp"));
	Plane->setMaterialFlag(video::EMF_LIGHTING, false);
	Plane->setMaterialFlag(video::EMF_FOG_ENABLE, false);

	// setup a simple positions array
	initGPos();


	// create the initial grass nodes
	createGrassNodes();
}

// simple eventhandler
class MyEventReceiver : public IEventReceiver
{
private:
public:
	MyEventReceiver() : IEventReceiver()
	{
	}

	virtual bool OnEvent(const SEvent& event)
	{
		switch (event.EventType)
		{
		case EEVENT_TYPE::EET_MOUSE_INPUT_EVENT:
			if (event.MouseInput.Event == EMIE_RMOUSE_PRESSED_DOWN)
				smgr->getActiveCamera()->setInputReceiverEnabled(!smgr->getActiveCamera()->isInputReceiverEnabled());
			break;
		case EEVENT_TYPE::EET_KEY_INPUT_EVENT:
			if (event.KeyInput.PressedDown)
			{
				switch (event.KeyInput.Key)
				{
				case KEY_KEY_Q: viewfromcamera = camera; break;
				case KEY_KEY_W: viewfromcamera = camera2; break;
				case KEY_KEY_1:
					GRASSMODE = MODE_NEWNODE;
					createGrassNodes();
					break;
				case KEY_KEY_2:
					GRASSMODE = MODE_HIDESHOW;
					createGrassNodes();
					break;
				case KEY_KEY_3:
					GRASSMODE = MODE_CREATENEW;
					createGrassNodes();
					break;
				}
			}
			break;
		}
		return false;
	}
};

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// everything below here is just setting up the program


MyEventReceiver receiver;

bool genericDemoSetup()
{
	// create device with full flexibility over creation parameters
	// you can add more parameters if desired, check irr::SIrrlichtCreationParameters
	irr::SIrrlichtCreationParameters params;
		params.DriverType = EDT_OPENGL;
		params.WindowSize = core::dimension2d<u32>(640, 480);
	device = createDeviceEx(params);
		if (device == 0) return false; 
			driver = device->getVideoDriver();
			smgr = device->getSceneManager();
			env = device->getGUIEnvironment();
			driver->setTextureCreationFlag(video::ETCF_ALWAYS_32_BIT, true);


	// create event receiver
	device->setEventReceiver(&receiver);

	return true;
}


int main()
{
	// setup the demo
	if (!genericDemoSetup()) return 1;

	// create all of the items specific to this demo
	createScene();

	// run the demo
	int lastFPS = -1;
	while (device->run())
		if (device->isWindowActive())
		{
			// update the grass render points
			updateGrass();

			driver->beginScene(video::ECBF_COLOR | video::ECBF_DEPTH, video::SColor(0));
			smgr->drawAll();
			env->drawAll();
			driver->endScene();

			// display frames per second in window title
			int fps = driver->getFPS();
			if (lastFPS != fps)
			{
				core::stringw str = "FPS:"; str += fps;
				str += "             #Primitive Drawn = "; str += driver->getPrimitiveCountDrawn();
				str += "               Method = ";
				if (GRASSMODE == MODE_NEWNODE) str += "IGE_GrassNode";
				if (GRASSMODE == MODE_HIDESHOW) str += "use visibility";
				if (GRASSMODE == MODE_CREATENEW) str += "create new nodes";
				device->setWindowCaption(str.c_str());
				
				
				lastFPS = fps;
			}
		}

	device->drop();

	return 0;
}


CuteAlien
Admin
Posts: 9734
Joined: Mon Mar 06, 2006 2:25 pm
Location: Tübingen, Germany
Contact:

Re: framerate issues

Post by CuteAlien »

1 is always the fastest here. Which is also what I expected - as you have just as many draw calls in 3, but additional overhead.
So no idea right now how it can be slower for your case.

Some code that really could use instance support in Irrlicht *sigh*
IRC: #irrlicht on irc.libera.chat
Code snippet repository: https://github.com/mzeilfelder/irr-playground-micha
Free racer made with Irrlicht: http://www.irrgheist.com/hcraftsource.htm
CuteAlien
Admin
Posts: 9734
Joined: Mon Mar 06, 2006 2:25 pm
Location: Tübingen, Germany
Contact:

Re: framerate issues

Post by CuteAlien »

One tiny optimization for 1 (thought it made no noticeable difference here) is to call Positions.set_used(0) instead of Positions.clear().
Unlike STL vector the Irrlicht array does by default release the memory on clear(). While set_used(0) behaves like STL vector::clear() and keeps the memory intact.
So that avoid re-allocations each frame.
IRC: #irrlicht on irc.libera.chat
Code snippet repository: https://github.com/mzeilfelder/irr-playground-micha
Free racer made with Irrlicht: http://www.irrgheist.com/hcraftsource.htm
CuteAlien
Admin
Posts: 9734
Joined: Mon Mar 06, 2006 2:25 pm
Location: Tübingen, Germany
Contact:

Re: framerate issues

Post by CuteAlien »

Frustum (and *not* frustrum, it's not frustrating ^_^) checks can be optimized a bit. First pass the frustum as const ref parameter to the function so you don't have to get it from camera for each point.
And second - you don't have to continue checking once a point is outside any plane. break out of the loop once you know it's false.

Thought doesn't matter for comparison obviously as long all options use the same.
IRC: #irrlicht on irc.libera.chat
Code snippet repository: https://github.com/mzeilfelder/irr-playground-micha
Free racer made with Irrlicht: http://www.irrgheist.com/hcraftsource.htm
Seven
Posts: 1034
Joined: Mon Nov 14, 2005 2:03 pm

Re: framerate issues

Post by Seven »

and I thought math was hard. It turns out english is just as bad :)
I will tweak as you suggested. thanks for the help!
Seven
Posts: 1034
Joined: Mon Nov 14, 2005 2:03 pm

Re: framerate issues

Post by Seven »

Currently rendering ~ 1million Primitives at > 200 FPS using OpenGL Driver in a windowed app (1034 x 768)!
very pleased with it all.

Image

Image
CuteAlien
Admin
Posts: 9734
Joined: Mon Mar 06, 2006 2:25 pm
Location: Tübingen, Germany
Contact:

Re: framerate issues

Post by CuteAlien »

Hehe,yeah, everyone gets the frustum/frustrum wrong at first (even Irrlicht in the past...).

Getting over 200 fps with so many elements is cool. Polygon numbers are often not so much the problem, but individual elements like grass can be hard.
IRC: #irrlicht on irc.libera.chat
Code snippet repository: https://github.com/mzeilfelder/irr-playground-micha
Free racer made with Irrlicht: http://www.irrgheist.com/hcraftsource.htm
Post Reply