Jump to content

Advanced Mesh creation and modification options


klepto2
 Share

Recommended Posts

While the Mesh API is very fast and also flexible, I think it could be more flexible. 

Background: 

I am working on this: 

 

and I have encountered a small issue with the mesh API. While you're able to modify vertices and indices on the fly it is not possible to change and update the size of these Vectors. I found a post where Josh describes this by design and i agree that from a performance point this is useful. On the other side it limits the usage of the class.  Lets take the "Dear ImGui' as a sample: 

As this is an intermediate Gui system the layout is not calculated once, but instead each frame, This means that a window with small content has a small vertex and index count, but when you modify (eg: expand trees or add text) the mesh is regenerated and the vertex size and index buffer changes in size.

Currently there are 2 ways to solve this: 

  1.  Recreate meshes on the fly (delete the old mesh, and create a new one with the new size)
    1. Surprisingly fast, but due to sync with the graphics card it leads to high flickering
  2. Create the meshes with hight vertex and inddex counts (lets say 10000 / 30000) and use the Mesh::Modify methods
    1. Works without flickering, but is extremly slow, not because of the modifcation itself, but the extreme size and unneeded uploads to the gpu.

I have tested something which i assumed will not work, but surprisingly this worked flawless with nearly no impact:

  1. I modfied mesh.h and changed the protected modifier to public
    1. This gave me access to the actual Vectors for vertices and indices
  2. With access to these Vectors i can now resize them at will.
  3. I give them just a small bigger size (around 300 items) than needed to not perform resizing every frame
  4. After that i just use the Mesh::Modify methods

Results:

  • with the recreation method i get around 150 to 180 fps, but flickering
  • with the preserved Size method i get 20 to 50 fps (depending on debug or release mode)
  • with the resizing method i get 180 to 240 fps 

So maybe the resizing should be added optionaly to the Modify methods or maybe another DynamicMesh class.

Another thing which might be useful for some  scenarios: 

Shared VertexBuffers per Model or MeshGroup:

Most Vulkan or modern DirectX/OpenGL use one VertexBuffer for big models, and just separate Indexbuffers per material.

In Ultraengine this could be used to further increase the GPU-Bandwidth: I could Imagine that you define a Main-Mesh and this mesh can be assigned to other Meshs as parent. Then these Child meshes only need to provide indices and the vertexbuffer is the same as the the parent mesh.

Use Cases: Lod, Voxel terrains etc.

 

  • Upvote 1
  • Intel® Core™ i7-8550U @ 1.80 Ghz 
  • 16GB RAM 
  • INTEL UHD Graphics 620
  • Windows 10 Pro 64-Bit-Version
Link to comment
Share on other sites

Ultra uses one big vertex buffer and one big 32-bit unsigned int indice buffer.

I am not ready to comment on the rest because I need to re-read it and think about it.

  • Like 2

My job is to make tools you love, with the features you want, and performance you can't live without.

Link to comment
Share on other sites

22 hours ago, klepto2 said:

Most Vulkan or modern DirectX/OpenGL use one VertexBuffer for big models, and just separate Indexbuffers per material.

Maybe I should clarify this a bit.  The Architecture of 3rd party libraries (including Dear ImGui, or most nvidia libs) normalliy provide a shared VertexStructure and then just an Indexbuffer for each "surface". To replicate this behaviour in UltraEngine, you currently have to create multiple meshs and recalculate the Vertexbuffer (I have done so for ImGui) or you need to duplicate the Vertexbuffer for each mesh.

The above thing is a nice to have but not essential.

What is most important for me is the abillity to resize the Vertices and Indices at will. While I understand that Ultraengine is layedout for high performance I believe that some parts should also be a bit flexible. (It doesn't cost to much if you just  need to resize every now and then and just upload small bits to the gpu instead of a big buffer where just a small bit is used anyway).

  • Upvote 1
  • Intel® Core™ i7-8550U @ 1.80 Ghz 
  • 16GB RAM 
  • INTEL UHD Graphics 620
  • Windows 10 Pro 64-Bit-Version
Link to comment
Share on other sites

I have found a very hackish workaround and this should not be used, unless you know exactly what you do:

class ModifiableMesh : public Mesh
	{
	public:
		void ResizeVertexBuffer(shared_ptr<Mesh> base, int newSize)
		{
			static_cast<ModifiableMesh*>(base.get())->Mesh::m_vertices.resize(newSize);
		};

		void ResizeIndexBuffer(shared_ptr<Mesh> base, int newSize)
		{
			static_cast<ModifiableMesh*>(base.get())->Mesh::m_indices.resize(newSize);
		};

		int VertexSize(shared_ptr<Mesh> base)
		{
			return static_cast<ModifiableMesh*>(base.get())->Mesh::m_vertices.size();
		};

		int IndexSize(shared_ptr<Mesh> base)
		{
			return static_cast<ModifiableMesh*>(base.get())->Mesh::m_indices.size();
		};
	};

	const shared_ptr<ModifiableMesh> ModMesh = std::make_shared<ModifiableMesh>();

with this you can now resize the Vectors even when they are protected:

int v_size = ModMesh->VertexSize(mesh);
int i_size = ModMesh->IndexSize(mesh);

int v_diff = v_size - newVertices.size() - INDEX_FLOAT_RANGE;
int i_diff = i_size - newIndices.size() - INDEX_FLOAT_RANGE;


if (abs(v_diff) > INDEX_FLOAT_RANGE)
{
	Print("Resizing (Vertex-Buffer): " + String(v_diff) + " | " +
		String(v_size) + "-" + String(newVertices.size()));

	ModMesh->ResizeVertexBuffer(mesh,newVertices.size() + INDEX_FLOAT_RANGE);
}

if (abs(i_diff) > INDEX_FLOAT_RANGE)//newIndices.size() > mesh->m_indices.size())
{
	Print("Resizing (Index-Buffer): " + String(i_diff) + " | " +
		String(i_size) + "-" + String(newIndices.size()));
	ModMesh->ResizeIndexBuffer(mesh,newIndices.size() + INDEX_FLOAT_RANGE);
}
mesh->Modify(newVertices);
mesh->Modify(newIndices);

Not really a good way, but it will do it as long as we have no ability to access the vectors directly.

  • Like 1
  • Intel® Core™ i7-8550U @ 1.80 Ghz 
  • 16GB RAM 
  • INTEL UHD Graphics 620
  • Windows 10 Pro 64-Bit-Version
Link to comment
Share on other sites

I encountered a similar problem with the Sprite::SetText method, and I solved it like this:

  1. If the new mesh data is bigger than the existing mesh, get rid of the existing mesh and create a new mesh.
  2. If the new mesh data is smaller or equal to the size of the existing mesh, copy the new mesh data to the existing mesh and set the extra vertices to all use position (0,0,0), so they will be invisible.

This is similar to how STL vectors resize when a bigger size is needed, but don't allocate new memory of the new size is smaller. Over times the resize events get more and more infrequent. You can also add some padding to minimize resizes as the mesh is growing.

The reason you can see flickering when a mesh changes is because the old visibility set is still being used, the old mesh goes out of scope and is deleted, but the new mesh has not cycled through the culling update yet. It's a tricky problem.

This problem demonstrates the major weakness of IMgui's design vs. the persistent objects the built-in GUI uses, but if people still want to use IMgui I don't mind at all. This makes a good test case of a problem we may encounter in other areas in the future.

This is not my complete answer, just some preliminary thoughts.

  • Like 1

My job is to make tools you love, with the features you want, and performance you can't live without.

Link to comment
Share on other sites

Maybe

1 hour ago, Josh said:

The reason you can see flickering when a mesh changes is because the old visibility set is still being used, the old mesh goes out of scope and is deleted, but the new mesh has not cycled through the culling update yet. It's a tricky problem.

Maybe a flag to disable the Culling step for particular models/meshes might do the trick, in Orthographic projection (especially GUIs) it might be useful to always assume the mesh is visible as long as the model itself is not hidden.

 

I was able to optimize the rendering to be flicker free and still performant. The previous flickering was due to a misconception on my side earlier. This is how Imgui provides the render data: 

  1. you notify ImGui that you will render a new frame
  2. you define the GUI 
  3. last you ask ImGui for the Renderdata
    1. The RenderData consists of Displaylists (for each window, foreground or background operation)
      1. The Displaylists itself contains the VertexBuffer and the indexbuffer and a commandlist 
      2. the commandlists specify which texture to use and which indices (by providing an Offset and an ElementCount)
    2. The Render data is ordered front to back representing the Z-Index.

In my first implementations i assumed that I need a separate Model for each command in the commandlists, and i reused these models and reordered them every frame. The Problem: if in the previous frame a big window with lots of content was rendered focused, but the next frame a small and simple window was in front. The Model of the big window was resized to the smaller window and vice versa. 

In the current implementation i only use one model per displaylist (which contains the _ownername) and add meshes according to the commandbuffer. So know i can identify which model belongs to a specific window (displaylist) and I only reuse this model for that particular window, when a window is closed I just hide the model and reuse it once the window is back.  If a window has fewer commands than before i clear it and rebuild als meshes needed. Otherwise i use the resize approach.

Surprisingly, this eliminates all the flickering but still maintains a good performance.

1 hour ago, Josh said:

I encountered a similar problem with the Sprite::SetText method, and I solved it like this:

  1. If the new mesh data is bigger than the existing mesh, get rid of the existing mesh and create a new mesh.
  2. If the new mesh data is smaller or equal to the size of the existing mesh, copy the new mesh data to the existing mesh and set the extra vertices to all use position (0,0,0), so they will be invisible.

This is what i do when the cmds are lower than the previously created meshes, but this only works by removing all meshes from the model and adding them again afterwards. There currently is no way to remove a specific mesh from a model.

  • Intel® Core™ i7-8550U @ 1.80 Ghz 
  • 16GB RAM 
  • INTEL UHD Graphics 620
  • Windows 10 Pro 64-Bit-Version
Link to comment
Share on other sites

image.thumb.png.d622f210f7117af3b8d7ae252d0b2142.png

This shows the current state: It is really impressive what you can do. There are tons of widgets available (the colored Texteditor is a 3rd party plugin which works nativly with the integration). 

Also Dear ImGui is meant to be used for tooling/prototyping and debugging, not for ingame usage. Therefore, there are other tools.

  • Like 2
  • Intel® Core™ i7-8550U @ 1.80 Ghz 
  • 16GB RAM 
  • INTEL UHD Graphics 620
  • Windows 10 Pro 64-Bit-Version
Link to comment
Share on other sites

Here is an example that demonstrates the problem. It's not exactly a bug, but it can become a problem in different situations...

#include "UltraEngine.h"

using namespace UltraEngine;

int main(int argc, const char* argv[])
{
    auto displays = GetDisplays();
    auto window = CreateWindow("Ultra Engine", 0, 0, 1280, 720, displays[0], WINDOW_CENTER | WINDOW_TITLEBAR);
    auto world = CreateWorld();
    auto framebuffer = CreateFramebuffer(window);

    auto cam = CreateCamera(world, PROJECTION_ORTHOGRAPHIC);
    cam->SetClearColor(0, 0, 1);
    cam->SetZoom(100);

    auto model = CreateModel(world);

    while (window->Closed() == false and window->KeyDown(KEY_ESCAPE) == false)
    {
        if (window->KeyDown(KEY_SPACE))
        {
            model->Clear();
            auto mesh = model->AddMesh();
            mesh->AddVertex(0, 0, 0);
            mesh->AddVertex(1, 0, 0);
            mesh->AddVertex(1, 1, 0);
            mesh->AddPrimitive(0, 2, 1);
            mesh->UpdateBounds();
            model->UpdateBounds();
        }
        world->Update();
        world->Render(framebuffer, false);
    }
    return 0;
}

 

My job is to make tools you love, with the features you want, and performance you can't live without.

Link to comment
Share on other sites

What you are doing with the mesh creation is actually not very different from the multiple sprites I am creating. Both are going to involve some amount of data sent to the GPU to update the visible geometry. I might try your approach later on, since all my geometry is just rectangles now.

My job is to make tools you love, with the features you want, and performance you can't live without.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

 Share

×
×
  • Create New...