Add IMeshSceneNode::setNodeRegistration to allow registering MeshSceneNodes to the SceneManager per buffer instead of per node

So far SceneManager always sorted Nodes per render stage.
Now we allow sorting per mesh-buffer per render stage by creating a new node for each mesh-buffer.
It's only supported for CMeshSceneNode so far.

This allows to enable better transparency sorting for meshes which have transparent buffers.
Previously those always got rendered in the order in which they got added and ignored mesh-buffer bounding-boxes, but just used the bbox of the full mesh. Now they can use the bbox for each meshbuffer which can sometimes avoid render errors.

Also depending on the scene this can be quite a bit faster because it can help reduce texture changes. We sort solid nodes per texture, but only per node. So nodes with several textures had a texture switch between rendering each meshbuffer. And those are rather expensive in Irrlicht right now (and we support no bindless textures yet...)

Lastly it's now also used to buffer the render-stage. Checking this twice (once in registering the node and once in render) constantly showed up in the profiler. Which was a bit surprising really, but anyway - now it's gone.

I tried to keep it working for all cases we had before (all kind of situations, like when people may want to call render() outside the SceneManager). But not (yet) supporting the case of changing the meshbuffers (adding or removing some) without calling setMesh() again. Reason is that this wasn't well supported before either (node materials never updated). So for now I just assume people will call setMesh() again when they change the mesh.

git-svn-id: svn://svn.code.sf.net/p/irrlicht/code/trunk@6483 dfc29bdd-3216-0410-991c-e03cc46cb475
This commit is contained in:
cutealien 2023-05-04 16:07:18 +00:00
parent 9679fa7006
commit c4504c1d48
12 changed files with 752 additions and 481 deletions

@ -1,6 +1,7 @@
--------------------------
Changes in 1.9 (not yet released)
- Add IMeshSceneNode::setNodeRegistration to allow registering MeshSceneNodes to the SceneManager per buffer instead of per node
- Add SMaterialLayer::hasSetTextureMatrix and SMaterialLayer::resetTextureMatrix
- Add IShaderConstantSetCallBack::OnCreate to allow earlier access to IMaterialRendererServices
- CIrrDeviceWin32::yield() now uses Sleep(0) instead of Sleep(1).

@ -15,6 +15,20 @@ namespace scene
class IShadowVolumeSceneNode;
class IMesh;
//! Option for nodes how to register themeselves at the SceneManager
enum ENodeRegistration
{
//! Each node registers once and renders all it's mesh-buffers
ENR_DEFAULT,
//! Register a new node per mesh-buffer at the SceneManager
//! It allows the SceneManager to sort in each render stage per buffer instead of per node.
//! This can be useful when having several transparent buffers in a mesh.
//! Depending on the scene (and hardware) this can have a positive or negative effect on performance.
//! It can avoid texture-switches, but adds nodes to sort and more matrix transformations are set.
ENR_PER_MESH_BUFFER
};
//! A scene node displaying a static mesh
class IMeshSceneNode : public ISceneNode
@ -28,9 +42,11 @@ public:
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,1,1))
: ISceneNode(parent, mgr, id, position, rotation, scale) {}
: ISceneNode(parent, mgr, id, position, rotation, scale)
, NodeRegistration(ENR_DEFAULT)
{}
//! Sets a new mesh to display
//! Sets a new mesh to display or update mesh when it changed
/** \param mesh Mesh to display. */
virtual void setMesh(IMesh* mesh) = 0;
@ -73,6 +89,23 @@ public:
/** This flag can be set by setReadOnlyMaterials().
\return Whether the materials are read-only. */
virtual bool isReadOnlyMaterials() const = 0;
//! Set how the node registers itself to the SceneManager
/** Note: Derived classes can ignore this flag, so think of it as a hint. */
virtual void setNodeRegistration(ENodeRegistration nodeRegistration)
{
NodeRegistration = nodeRegistration;
}
//! How does a node register itself to the SceneManager
/** Note: Derived classes may ignore this flag */
virtual ENodeRegistration getNodeRegistration() const
{
return NodeRegistration;
}
protected:
ENodeRegistration NodeRegistration;
};
} // end namespace scene

@ -66,7 +66,7 @@ namespace scene
//! In this pass, lights are transformed into camera space and added to the driver
ESNRP_LIGHT =2,
//! This is used for sky boxes.
//! This is mostly used for sky boxes. Stage between light and solid.
ESNRP_SKY_BOX =4,
//! All normal objects can use this for registering themselves.

@ -0,0 +1,185 @@
// Copyright (C) 2002-2012 Nikolaus Gebhardt
// This file is part of the "Irrlicht Engine".
// For conditions of distribution and use, see copyright notice in irrlicht.h
#include "IMeshSceneNode.h"
#include "IVideoDriver.h"
#include "ISceneManager.h"
#include "ISceneNode.h"
namespace irr
{
namespace scene
{
// A node which helps rendering a single buffer of an IMeshSceneNode
// It solves several problems:
// - Allowing the scene manager to sort meshbuffers. Currently it only sorts nodes, so we have to put each meshbuffer in an extra node to allow that
// The reason we want that is
// a) Better sorting when a node has several transparent buffers (without it they will be just drawn in original order)
// b) It can allow to avoid texture changes in the render-pipeline which can make quite a bit of a performance difference
// - It buffers the RenderPass. Bit of an abuse of this interface maybe?
// Strangely the check for finding out the correct render pass constantly shows up in profilers.
// Not exactly sure why as the check looks pretty cheap. My best guess is that there are some cache misses going on due to several virtual
// function pointers being involved in the transparency check.
//
// For now (added pre Irrlicht 1.9) this interface is still a bit experimental. Maybe could go into public headers later, not sure yet.
// Or maybe the SceneManager shouldn't work with nodes at all but a simplified interface to render buffers from which Nodes can derive?
// CBufferRenderNode isn't really a node - it has to work around nearly all the ISceneNode functions, it only has to be one because
// the SceneManager can't sort anything else but nodes.
class CBufferRenderNode : public ISceneNode
{
public:
CBufferRenderNode(irr::scene::IMeshSceneNode& parent, irr::scene::ISceneManager* mgr, irr::u32 bufferIdx)
: ISceneNode(0, mgr) // we don't want it in the scenegraph
, MeshNodeParent(parent)
, BufferIdx(bufferIdx)
, RenderPass(ESNRP_NONE)
, ParentDoesRender(true)
{
// While it's not the parent in the SceneGraph, we still want to allow accessing it
// That can be useful p.E. in a light manager
// Arguably if it's a good idea as it's a bit going against the ISceneNode interface - having a parent which doesn't have this node as child.
// But the alternative is adding another member to the ISceneNode or having SceneManager not use the ISceneNode for rendering
// So for now it should be fine, but if that interface ever get's public... we might have to reconsider
Parent = &MeshNodeParent;
}
u32 prepareRendering(E_SCENE_NODE_RENDER_PASS pass, bool parentDoesRender)
{
RenderPass = pass;
ParentDoesRender = parentDoesRender;
if ( !ParentDoesRender )
return SceneManager->registerNodeForRendering(this, pass);
return 0;
}
E_SCENE_NODE_RENDER_PASS getRenderPass() const
{
return RenderPass;
}
// When true render() this node hasn't registered itself for rendering, but expects it's owner to do the rendering
bool getDoesParentRender() const
{
return ParentDoesRender;
}
// Render meshbuffer, but don't set transformation
// It's assumed that this function is only called from within the correct render stage
void renderBuffer(video::IVideoDriver* driver)
{
const IMeshBuffer* mb = MeshNodeParent.getMesh()->getMeshBuffer(BufferIdx);
if (mb)
{
const video::SMaterial& material = MeshNodeParent.getMaterial(BufferIdx);
driver->setMaterial(material);
driver->drawMeshBuffer(mb);
}
}
//! Renders the node.
virtual void render() IRR_OVERRIDE
{
video::IVideoDriver* driver = SceneManager->getVideoDriver();
driver->setTransform(video::ETS_WORLD, MeshNodeParent.getAbsoluteTransformation());
renderBuffer(driver);
// resetting each time so direct calls to render() for parent node continue to work
RenderPass = ESNRP_NONE;
ParentDoesRender = true;
}
virtual const core::aabbox3d<f32>& getBoundingBox() const IRR_OVERRIDE
{
return MeshNodeParent.getMesh()->getMeshBuffer(BufferIdx)->getBoundingBox();
}
virtual video::SMaterial& getMaterial(u32 num) IRR_OVERRIDE
{
return MeshNodeParent.getMaterial(BufferIdx);
}
virtual u32 getMaterialCount() const IRR_OVERRIDE
{
return 1;
}
virtual const core::matrix4& getAbsoluteTransformation() const IRR_OVERRIDE
{
return MeshNodeParent.getAbsoluteTransformation();
}
virtual const core::aabbox3d<f32> getTransformedBoundingBox() const IRR_OVERRIDE
{
core::aabbox3d<f32> box = getBoundingBox();
getAbsoluteTransformation().transformBoxEx(box);
return box;
}
virtual void getTransformedBoundingBoxEdges(core::array< core::vector3d<f32> >& edges) const IRR_OVERRIDE
{
edges.set_used(8);
getBoundingBox().getEdges( edges.pointer() );
for ( u32 i=0; i<8; ++i )
getAbsoluteTransformation().transformVect( edges[i] );
}
virtual core::matrix4 getRelativeTransformation() const IRR_OVERRIDE
{
return MeshNodeParent.getRelativeTransformation();
}
virtual s32 getID() const IRR_OVERRIDE
{
return MeshNodeParent.getID();
}
virtual const core::vector3df& getScale() const IRR_OVERRIDE
{
return MeshNodeParent.getScale();
}
virtual const core::vector3df& getRotation() const IRR_OVERRIDE
{
return MeshNodeParent.getRotation();
}
virtual const core::vector3df& getPosition() const IRR_OVERRIDE
{
return MeshNodeParent.getPosition();
}
virtual core::vector3df getAbsolutePosition() const IRR_OVERRIDE
{
return MeshNodeParent.getAbsolutePosition();
}
virtual ITriangleSelector* getTriangleSelector() const IRR_OVERRIDE
{
return MeshNodeParent.getTriangleSelector();
}
// Not allowing any of that stuff
virtual void OnRegisterSceneNode()IRR_OVERRIDE {}
virtual void OnAnimate(u32 timeMs) IRR_OVERRIDE {}
virtual void addChild(ISceneNode* child) IRR_OVERRIDE {}
virtual void addAnimator(ISceneNodeAnimator* animator) IRR_OVERRIDE {}
virtual void setScale(const core::vector3df& scale) IRR_OVERRIDE {}
virtual void setRotation(const core::vector3df& rotation) IRR_OVERRIDE {}
virtual void setPosition(const core::vector3df& newpos) IRR_OVERRIDE {}
virtual void setParent(ISceneNode* newParent) IRR_OVERRIDE {}
virtual void setTriangleSelector(ITriangleSelector* selector) IRR_OVERRIDE {}
virtual void updateAbsolutePosition() IRR_OVERRIDE {}
virtual void serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options=0) const IRR_OVERRIDE {}
virtual void deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options=0) IRR_OVERRIDE {}
private:
irr::scene::IMeshSceneNode& MeshNodeParent;
irr::u32 BufferIdx; // Note: Not saving the meshbuffer pointer as meshes can add/remove buffers and we don't want to keep track of that
E_SCENE_NODE_RENDER_PASS RenderPass;
bool ParentDoesRender;
};
} // end namespace scene
} // end namespace irr

@ -3,6 +3,7 @@
// For conditions of distribution and use, see copyright notice in irrlicht.h
#include "CMeshSceneNode.h"
#include "CBufferRenderNode.h"
#include "IVideoDriver.h"
#include "ISceneManager.h"
#include "S3DVertex.h"
@ -22,14 +23,12 @@ namespace irr
namespace scene
{
//! constructor
CMeshSceneNode::CMeshSceneNode(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), Shadow(0),
PassCount(0), ReadOnlyMaterials(false)
: IMeshSceneNode(parent, mgr, id, position, rotation, scale)
, Mesh(0), Shadow(0), ReadOnlyMaterials(false)
{
#ifdef _DEBUG
setDebugName("CMeshSceneNode");
@ -42,6 +41,7 @@ CMeshSceneNode::CMeshSceneNode(IMesh* mesh, ISceneNode* parent, ISceneManager* m
//! destructor
CMeshSceneNode::~CMeshSceneNode()
{
setUsedBufferRenderNodes(0);
if (Shadow)
Shadow->drop();
if (Mesh)
@ -54,39 +54,60 @@ void CMeshSceneNode::OnRegisterSceneNode()
{
if (IsVisible && Mesh)
{
// because this node supports rendering of mixed mode meshes consisting of
Box = Mesh->getBoundingBox(); // in case mesh was modified, as clipping happens when registering nodes for rendering
// Because this node supports rendering of mixed mode meshes consisting of
// transparent and solid material at the same time, we need to go through all
// materials, check of what type they are and register this node for the right
// render pass according to that.
// Also some buffers might register into their own render node
video::IVideoDriver* driver = SceneManager->getVideoDriver();
PassCount = 0;
int transparentCount = 0;
int solidCount = 0;
// count transparent and solid materials in this scene node
const u32 numMaterials = ReadOnlyMaterials ? Mesh->getMeshBufferCount() : Materials.size();
for (u32 i=0; i<numMaterials; ++i)
if ( !(DebugDataVisible & scene::EDS_HALF_TRANSPARENCY) )
{
const video::SMaterial& material = ReadOnlyMaterials ? Mesh->getMeshBuffer(i)->getMaterial() : Materials[i];
// count transparent and solid materials in this scene node
const u32 numMaterials = ReadOnlyMaterials ? Mesh->getMeshBufferCount() : Materials.size();
const bool parentRenders = NodeRegistration == ENR_DEFAULT || numMaterials < 2;
for (u32 i=0; i<numMaterials; ++i)
{
const video::SMaterial& material = ReadOnlyMaterials ? Mesh->getMeshBuffer(i)->getMaterial() : Materials[i];
if ( driver->needsTransparentRenderPass(material) )
++transparentCount;
else
++solidCount;
if (solidCount && transparentCount)
break;
if ( driver->needsTransparentRenderPass(material) )
{
BufferRenderNodes[i]->prepareRendering(ESNRP_TRANSPARENT, parentRenders);
if ( parentRenders )
{
++transparentCount;
}
}
else
{
BufferRenderNodes[i]->prepareRendering(ESNRP_SOLID, parentRenders);
if ( parentRenders )
{
++solidCount;
}
}
}
}
// register according to material types counted
if (solidCount)
SceneManager->registerNodeForRendering(this, scene::ESNRP_SOLID);
SceneManager->registerNodeForRendering(this, ESNRP_SOLID);
if (transparentCount)
SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT);
SceneManager->registerNodeForRendering(this, ESNRP_TRANSPARENT);
if (Shadow) // update (not render) shadow node after lights have been set up
SceneManager->registerNodeForRendering(this, ESNRP_SKY_BOX);
if (DebugDataVisible) // debug data has it's own render-stage between solid and transparence
SceneManager->registerNodeForRendering(this, ESNRP_SHADOW);
ISceneNode::OnRegisterSceneNode();
}
@ -96,109 +117,110 @@ void CMeshSceneNode::OnRegisterSceneNode()
//! renders the node.
void CMeshSceneNode::render()
{
video::IVideoDriver* driver = SceneManager->getVideoDriver();
if (!Mesh || !driver)
if (!Mesh )
return;
const bool isTransparentPass =
SceneManager->getSceneNodeRenderPass() == scene::ESNRP_TRANSPARENT;
const E_SCENE_NODE_RENDER_PASS renderPass = SceneManager->getSceneNodeRenderPass();
++PassCount;
driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
Box = Mesh->getBoundingBox();
if (Shadow && PassCount==1)
Shadow->updateShadowVolumes();
// for debug purposes only:
bool renderMeshes = true;
video::SMaterial mat;
if (DebugDataVisible && PassCount==1)
if ( renderPass == ESNRP_SKY_BOX )
{
// overwrite half transparency
if (DebugDataVisible & scene::EDS_HALF_TRANSPARENCY)
{
for (u32 g=0; g<Mesh->getMeshBufferCount(); ++g)
{
mat = Materials[g];
mat.MaterialType = video::EMT_TRANSPARENT_ADD_COLOR;
driver->setMaterial(mat);
driver->drawMeshBuffer(Mesh->getMeshBuffer(g));
}
renderMeshes = false;
}
if (Shadow )
Shadow->updateShadowVolumes();
}
// render original meshes
if (renderMeshes)
else if ( renderPass == ESNRP_SHADOW )
{
for (u32 i=0; i<Mesh->getMeshBufferCount(); ++i)
// for debug purposes only
if ( DebugDataVisible )
{
scene::IMeshBuffer* mb = Mesh->getMeshBuffer(i);
if (mb)
video::IVideoDriver* driver = SceneManager->getVideoDriver();
driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
// render with half transparency
if (DebugDataVisible & scene::EDS_HALF_TRANSPARENCY)
{
const video::SMaterial& material = ReadOnlyMaterials ? mb->getMaterial() : Materials[i];
const bool transparent = driver->needsTransparentRenderPass(material);
// only render transparent buffer if this is the transparent render pass
// and solid only in solid pass
if (transparent == isTransparentPass)
for (u32 g=0; g<Mesh->getMeshBufferCount(); ++g)
{
driver->setMaterial(material);
driver->drawMeshBuffer(mb);
irr::video::SMaterial mat = Materials[g];
mat.MaterialType = video::EMT_TRANSPARENT_ADD_COLOR;
driver->setMaterial(mat);
driver->drawMeshBuffer(Mesh->getMeshBuffer(g));
}
}
video::SMaterial m;
m.Lighting = false;
m.AntiAliasing=0;
driver->setMaterial(m);
if (DebugDataVisible & scene::EDS_BBOX)
{
driver->draw3DBox(Box, video::SColor(255,255,255,255));
}
if (DebugDataVisible & scene::EDS_BBOX_BUFFERS)
{
for (u32 g=0; g<Mesh->getMeshBufferCount(); ++g)
{
driver->draw3DBox(
Mesh->getMeshBuffer(g)->getBoundingBox(),
video::SColor(255,190,128,128));
}
}
if (DebugDataVisible & scene::EDS_NORMALS)
{
// draw normals
const f32 debugNormalLength = SceneManager->getParameters()->getAttributeAsFloat(DEBUG_NORMAL_LENGTH);
const video::SColor debugNormalColor = SceneManager->getParameters()->getAttributeAsColor(DEBUG_NORMAL_COLOR);
const u32 count = Mesh->getMeshBufferCount();
for (u32 i=0; i != count; ++i)
{
driver->drawMeshBufferNormals(Mesh->getMeshBuffer(i), debugNormalLength, debugNormalColor);
}
}
// show mesh
if (DebugDataVisible & scene::EDS_MESH_WIRE_OVERLAY)
{
m.Wireframe = true;
driver->setMaterial(m);
for (u32 g=0; g<Mesh->getMeshBufferCount(); ++g)
{
driver->drawMeshBuffer(Mesh->getMeshBuffer(g));
}
}
}
}
// for debug purposes only:
if (DebugDataVisible && PassCount==1)
else // solid, transparent or unknown (none when render is called without SceneManager) render stages
{
video::SMaterial m;
m.Lighting = false;
m.AntiAliasing=0;
driver->setMaterial(m);
video::IVideoDriver* driver = SceneManager->getVideoDriver();
driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
if (DebugDataVisible & scene::EDS_BBOX)
// render buffers, or at least those which don't render in their own node
for (u32 i=0; i<BufferRenderNodes.size(); ++i)
{
driver->draw3DBox(Box, video::SColor(255,255,255,255));
}
if (DebugDataVisible & scene::EDS_BBOX_BUFFERS)
{
for (u32 g=0; g<Mesh->getMeshBufferCount(); ++g)
CBufferRenderNode* bufRenderNode = BufferRenderNodes[i];
if ( bufRenderNode->getDoesParentRender())
{
driver->draw3DBox(
Mesh->getMeshBuffer(g)->getBoundingBox(),
video::SColor(255,190,128,128));
}
}
E_SCENE_NODE_RENDER_PASS bufferRenderPass = bufRenderNode->getRenderPass();
if (DebugDataVisible & scene::EDS_NORMALS)
{
// draw normals
const f32 debugNormalLength = SceneManager->getParameters()->getAttributeAsFloat(DEBUG_NORMAL_LENGTH);
const video::SColor debugNormalColor = SceneManager->getParameters()->getAttributeAsColor(DEBUG_NORMAL_COLOR);
const u32 count = Mesh->getMeshBufferCount();
// render() called without OnRegisterSceneNode, but still wants to render in a specific render stage
// Note: Not checking transparency every time, as check got slightly expensive (I think it's prone to cache-misses)
if ( bufferRenderPass == ESNRP_NONE && renderPass > ESNRP_NONE )
{
if ( driver->needsTransparentRenderPass(getMaterial(i)) )
{
bufferRenderPass = ESNRP_TRANSPARENT;
}
else
{
bufferRenderPass = ESNRP_SOLID;
}
}
for (u32 i=0; i != count; ++i)
{
driver->drawMeshBufferNormals(Mesh->getMeshBuffer(i), debugNormalLength, debugNormalColor);
}
}
// show mesh
if (DebugDataVisible & scene::EDS_MESH_WIRE_OVERLAY)
{
m.Wireframe = true;
driver->setMaterial(m);
for (u32 g=0; g<Mesh->getMeshBufferCount(); ++g)
{
driver->drawMeshBuffer(Mesh->getMeshBuffer(g));
if ( bufRenderNode->getRenderPass() == renderPass )
bufRenderNode->renderBuffer(driver);
}
}
}
@ -207,7 +229,7 @@ void CMeshSceneNode::render()
//! Removes a child from this scene node.
//! Implemented here, to be able to remove the shadow properly, if there is one,
//! or to remove attached childs.
//! or to remove attached children.
bool CMeshSceneNode::removeChild(ISceneNode* child)
{
if (child && Shadow == child)
@ -256,6 +278,22 @@ u32 CMeshSceneNode::getMaterialCount() const
return Materials.size();
}
void CMeshSceneNode::setUsedBufferRenderNodes(irr::u32 num)
{
if ( BufferRenderNodes.size() > num )
{
for ( irr::u32 i=num; i<BufferRenderNodes.size(); ++i )
BufferRenderNodes[i]->drop();
BufferRenderNodes.erase(num, BufferRenderNodes.size()-num);
}
else if ( BufferRenderNodes.size() < num )
{
for ( irr::u32 i=BufferRenderNodes.size(); i < num; ++i )
{
BufferRenderNodes.push_back( new CBufferRenderNode(*this, SceneManager, i) );
}
}
}
//! Sets a new mesh
void CMeshSceneNode::setMesh(IMesh* mesh)
@ -264,10 +302,16 @@ void CMeshSceneNode::setMesh(IMesh* mesh)
{
mesh->grab();
if (Mesh)
{
Mesh->drop();
}
Mesh = mesh;
// Note: Mesh can change amount of meshbuffers later and we don't handle that so far so that would cause trouble
// For now assuming users call setMesh again in that case
copyMaterials();
setUsedBufferRenderNodes(Mesh ? Mesh->getMeshBufferCount() : 0);
}
}

@ -12,6 +12,7 @@ namespace irr
{
namespace scene
{
class CBufferRenderNode;
class CMeshSceneNode : public IMeshSceneNode
{
@ -83,6 +84,7 @@ namespace scene
protected:
void setUsedBufferRenderNodes(irr::u32 num);
void copyMaterials();
core::array<video::SMaterial> Materials;
@ -92,8 +94,9 @@ namespace scene
IMesh* Mesh;
IShadowVolumeSceneNode* Shadow;
s32 PassCount;
bool ReadOnlyMaterials;
core::array<scene::CBufferRenderNode*> BufferRenderNodes;
};
} // end namespace scene

File diff suppressed because it is too large Load Diff

@ -1038,6 +1038,7 @@
<ClInclude Include="burning_shader_compile_verify.h" />
<ClInclude Include="CB3DMeshWriter.h" />
<ClInclude Include="CBlit.h" />
<ClInclude Include="CBufferRenderNode.h" />
<ClInclude Include="CD3D9RenderTarget.h" />
<ClInclude Include="CDefaultSceneNodeAnimatorFactory.h" />
<ClInclude Include="CDefaultSceneNodeFactory.h" />

@ -1444,6 +1444,9 @@
<ClInclude Include="..\..\include\ESceneNodeUpdateAbs.h">
<Filter>include\scene</Filter>
</ClInclude>
<ClInclude Include="CBufferRenderNode.h">
<Filter>Irrlicht\scene\sceneNodes</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="..\..\changes.txt">

@ -776,6 +776,7 @@
<ClInclude Include="burning_shader_compile_verify.h" />
<ClInclude Include="CB3DMeshWriter.h" />
<ClInclude Include="CBlit.h" />
<ClInclude Include="CBufferRenderNode.h" />
<ClInclude Include="CD3D9RenderTarget.h" />
<ClInclude Include="CDefaultSceneNodeAnimatorFactory.h" />
<ClInclude Include="CDefaultSceneNodeFactory.h" />

@ -1438,6 +1438,9 @@
<ClInclude Include="..\..\include\EDeviceTypes.h">
<Filter>include</Filter>
</ClInclude>
<ClInclude Include="CBufferRenderNode.h">
<Filter>Irrlicht\scene\sceneNodes</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="..\..\changes.txt">

@ -1,4 +1,4 @@
Tests finished. 72 tests of 72 passed.
Compiled as DEBUG
Test suite pass at GMT Tue May 02 18:39:02 2023
Test suite pass at GMT Thu May 04 15:46:30 2023