ocornut / imgui

Dear ImGui: Bloat-free Graphical User interface for C++ with minimal dependencies

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

ImDrawList: Support for large meshes (64k+ vertices) with 16-bits indices

ocornut opened this issue · comments

commented

Info: I'll be pushing a change to support large meshes (64k+ vertices) with 16-bits indices.

TL;DR;

  • No breakage of existing renderer back-ends.
  • New feature needs to be supported by renderer back-end. When enabled, the total mesh limit of 64k vertices doesn't apply anymore.
  • If you've been using 32-bits indices, I would appreciate if you try to revert back to 16-bit indices and use this feature (need to update your renderer).

Until now wiith 16-bits indices (default compile-time setting) the code would assert when trying to create a ImDrawList with more than 64k vertices, and visuals would get glitched.
The solution until now was to enable 32-bits indices by using #define ImDrawIdx unsigned int in imconfig.h. However some hardware and api don't support 32-bits indices and this needed to be a compile-time setting I've been asked to come with a different solution.

The new solution comes in the form of adding VtxOffset field to ImDrawCmd.

// Pre 1.71 back-ends will typically ignore the VtxOffset/IdxOffset fields. When (io.BackendFlags & ImGuiBackendFlags_HasVtxOffset) 
// is enabled, those fields allow us to render meshes larger than 64K vertices while keeping 16-bits indices.
struct ImDrawCmd
{
    [....]
    unsigned int VtxOffset; // Start offset in vertex buffer. Pre-1.71 or without ImGuiBackendFlags_HasVtxOffset: always 0. With ImGuiBackendFlags_HasVtxOffset: may be >0 to support meshes larger than 64K vertices with 16-bits indices.

The feature needs the renderer to be able to offset the base vertex number, which is not available in some api (GL ES 2) so this is optional. Making it optional also ensure zero breakage of existing changes.

The back-end needs to set:

// Renderer Back-end supports ImDrawCmd::VtxOffset. This enables output of large meshes (64K+ vertices) while still using 16-bits indices.
ImGuiBackendFlags_RendererHasVtxOffset          = 1 << 3    

Note that I've also added an IdxOffset field in ImDrawCmd, which is the equivalent of the summing of cmd->ElemCount that every render loop already did.

struct ImDrawCmd
{
    [....]
    unsigned int IdxOffset; // Start offset in index buffer. Always equal to sum of ElemCount drawn so far.

Commit for this change in core imgui is: d1e8b69
Commit for supporting this in examples renderers is: b3dd03f

Feedback welcome. I don't rule out that there may be an issue with this.

@warrenm @bear24rw Would you mind adding corresponding support in the Metal back-end? It should be very easy but as I can't test Metal at the moment. You can refer for commit b3dd03f for how it was added to the other back-ends. Thank you very much!

(EDIT Renamed new flag to ImGuiBackendFlags_RendererHasVtxOffset)

commented

You can use this code to quickly check if large meshes are supported by your setup:

void ShowBackendCheckerWindow(bool* p_open)
{
    if (!ImGui::Begin("Dear ImGui Backend Checker", p_open))
    {
        ImGui::End();
        return;
    }

    ImGuiIO& io = ImGui::GetIO();
    ImGui::Text("Dear ImGui %s Backend Checker", ImGui::GetVersion());
    ImGui::Text("io.BackendPlatformName: %s", io.BackendPlatformName ? io.BackendPlatformName : "NULL");
    ImGui::Text("io.BackendRendererName: %s", io.BackendRendererName ? io.BackendRendererName : "NULL");
    ImGui::Separator();
    
    if (ImGui::TreeNode("0001: Renderer: Large Mesh Support"))
    {
        ImDrawList* draw_list = ImGui::GetWindowDrawList();
        {
            static int vtx_count = 60000;
            ImGui::SliderInt("VtxCount##1", &vtx_count, 0, 100000);
            ImVec2 p = ImGui::GetCursorScreenPos();
            for (int n = 0; n < vtx_count / 4; n++)
            {
                float off_x = (float)(n % 100) * 3.0f;
                float off_y = (float)(n % 100) * 1.0f;
                ImU32 col = IM_COL32(((n * 17) & 255), ((n * 59) & 255), ((n * 83) & 255), 255);
                draw_list->AddRectFilled(ImVec2(p.x + off_x, p.y + off_y), ImVec2(p.x + off_x + 50, p.y + off_y + 50), col);
            }
            ImGui::Dummy(ImVec2(300 + 50, 100 + 50));
            ImGui::Text("VtxBuffer.Size = %d", draw_list->VtxBuffer.Size);
        }
        {
            static int vtx_count = 60000;
            ImGui::SliderInt("VtxCount##2", &vtx_count, 0, 100000);
            ImVec2 p = ImGui::GetCursorScreenPos();
            for (int n = 0; n < vtx_count / (10*4); n++)
            {
                float off_x = (float)(n % 100) * 3.0f;
                float off_y = (float)(n % 100) * 1.0f;
                ImU32 col = IM_COL32(((n * 17) & 255), ((n * 59) & 255), ((n * 83) & 255), 255);
                draw_list->AddText(ImVec2(p.x + off_x, p.y + off_y), col, "ABCDEFGHIJ");
            }
            ImGui::Dummy(ImVec2(300 + 50, 100 + 20));
            ImGui::Text("VtxBuffer.Size = %d", draw_list->VtxBuffer.Size);
        }
        ImGui::TreePop();
    }

    ImGui::End();
}

image

@ocornut , I opened a pull request (#2592) to add this to the Metal backend. I don't have a large project to test it on so it was only tested with the testcase you posted above.

commented

Everyone: the current version has a bug with Columns, working on fixing it now.

commented

Everyone: the current version has a bug with Columns, working on fixing it now.

Fixed now.

commented

Closing this now!

Hello, I have Dear Imgui v1.75 configured with my project, but I see the assert IM_ASSERT(draw_list->_VtxCurrentIdx < (1 << 16) && "Too many vertices in ImDrawList using 16-bit indices. Read comment above"); is still present. Despite the fact that ImDrawIdx is 16bits, draw_list->_VtxCurrentIdx is 32bit and hence, when I remove this assert (I haven't touched imconfig.h) my mesh renders all the vertex labels (~130k elements).

Would the way to proceed be to remove the assert in order to avoid changing the data type of ImDrawIdx? I suppose I'm not understanding how the vertex offset is being used if draw_list->_VtxCurrentIdx is already being defined as an unsigned int (in imgui.h).

commented

@gmin7 have you read the comment above that assert in its entirety, as well as this thread?
If ImDrawIdx is 16-bit and you want to support large mesh, your back-end needs to support the ImGuiBackendFlags_RendererHasVtxOffset flag. You are not specifying your back-end nor how you end up with 130k element in a same window, will be surprisingly high.

The assert is there for a reason, if you remove it your display will be incorrect.

Hello Omar, I hope you are fine.

I am exploring ImGUI for a Seismic data visualization project compiled for WebAssembly.

I want to display the seismic data gathered in several lines, and the size of the data can be big (for example, if you record 4seconds
of seismic data sampling at 8.000 Hz in each microphone you'll need to draw a line with 32.000 points per microphone, and you
can have 50 microphones in a simple 2D seismic survey, so we are talking o about 1.600.000 points in a single plot),
and this are conservative sizes, try to figure out 3D surveys...

The problem starts when I try to port the ImGui desktop application (SDL2, OpenGL 3 - 32 bit indexes) to emscripten (WebAssembly) which uses WebGL 1.0 (OpenGL ES 2.0 - 16bits indexes).
Plots with big data (64K+ points) makes the WebAsembly application to freezes (or crash). This is why i came up to this page.

I have tried the solution you said above in my desktop application (trying to use a single code base working in both platforms)
but after making the following two changes in the desktop version to convert it to a 16 bit indexes version:
1.- Putting
io->ConfigFlags |= ImGuiBackendFlags_RendererHasVtxOffset;

2.- And commenting:
// #define ImDrawIdx unsigned int

I get a segmentation fault when I try to use your backend checker routine with 16bits indexes in the desktop:

seismic3: /home/eduardo/cpp/seismic-project/seismic-desktop/libs/imgui.cpp:4033: void AddDrawListToDrawData(ImVector<ImDrawList*>*, ImDrawList*): Assertion `draw_list->_VtxCurrentIdx < (1 << 16) && "Too many vertices in ImDrawList using 16-bit indices. Read comment above"' failed.
Aborted (core dumped)

In the desktop app (using your code for the backend checker),

  • The backend platform name is: imgui_impl_sdl
  • The backend renderer name is: imgui_impl_opengl3

For WebAssembly, emscripten implements by default WebGL 1.0, so the renderer will have no more than 16bits for the indexes.
You can view my test app at:
My Web Assembly Test App

Use the the View menu to open the Backend checker window with your code. You won't be able to display more than 64K vrtx per window. The Backend checker is not crashing but some bad graphic artifacts will appear above 64K+ vertices.
Is it possible to have 64K+ vertex per window?
What could I be doing wrong? Any idea?. I will continue the testing to give you the evidence you need.
I can share my code if you need so.
Many thanks in advanced for your kind attention.
Eduardo Yanez.

commented

@ejyanezp
io->ConfigFlags |= ImGuiBackendFlags_RendererHasVtxOffset;
This is incorrect, you should use
io->BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset;

Is it possible to have 64K+ vertex per window?

Yes using the above and this will require your backend to actually support that flag.

Generally speaking you should probably look at https://github.com/epezent/implot

Hello Omar, thanks for your answer and your time.

BTW, I am already using implot, and I like it :), but thanks for your suggestions! :) They are welcome!.

Now, this time I made the change correctly for my linux desktop App (for testing before the WebAssembly version):
io->BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset;
Commented again (imconfig.h):
// #define ImDrawIdx unsigned int

Then, I am not getting a segmentation fault, but the resulting images get glitched :(
Bad render - 1
Bad render - 2

Should be somehing like (this is with 32bits):
Good render

So, the question is:
How could I be sure that the backend supports the "ImGuiBackendFlags_RendererHasVtxOffset"?
I used the "Backend checker window" but with 16bits indexes I cannot get good results neither in the desktop app nor in the webassembly's:
BackendChecker

Any ideas, or any test you suggest, i will appreciated them.
Regards!

Hi Omar, you said:
"... need to update your renderer"
What does it mean specifically? I am not sure what actions imply "update your renderer". Could you please clarify?
My rendered as the "Backend checker" window reports is: imgui_impl_opengl3
So I guess I have just to recompile "imgui_impl_opengl3.cpp" with the new settings, right? (I already did it since the beginning, but no success yet rendering more than 64K vertices in a single window).
Regards,
e.

If backend declare support for ImGuiBackendFlags_RendererHasVtxOffset, it must hande ImDrawCmd::VtxOffset. For example:

g_pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, pcmd->VtxOffset + global_vtx_offset, 0, (UINT)cmd_list->VtxBuffer.Size, pcmd->IdxOffset + global_idx_offset, pcmd->ElemCount / 3);

Thanks for your answer @thedmd,
The renderer I am using is imgui_impl_opengl3
That renderer has the following lines imgui_impl_opengl3.cpp (lines: 375-380)

#if IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET
if (g_GlVersion >= 320)
   glDrawElementsBaseVertex(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, (void*)(intptr_t)(pcmd->IdxOffset * sizeof(ImDrawIdx)), (GLint)pcmd->VtxOffset);
else
#endif
   glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, (void*)(intptr_t)(pcmd->IdxOffset * sizeof(ImDrawIdx)));

The command I am using to know the OpenGL version in my Ubuntu 20.04 machine is:
$ glxinfo | grep "OpenGL version"

The result is:
OpenGL version string: 3.0 Mesa 20.0.8

So, if the code says
if (g_GlVersion >= 320)
I guess I cannot use 16bit indexes, is not possible, because I do not have 3.2+ version.

On the other side, my question is because I want to render more than 64K vertices in internet browsers using WebAssembly and emscripten. So I guess that currently is not possible either, the following comment is at the beginning of the file imgui_impl_opengl3.cpp:

// [x] Renderer: Desktop GL only: Support for large meshes (64k+ vertices) with 16-bit indices.

So, what alternatives do I have to render 64k+ vertices in a WebAssembly application?.

Perhaps imgui is not the way to go? :(

Thanks!

All imgui_impl_XXX.cpp are examples and usually serve as convenient way to integrate ImGui with an application.

imgui_impl_opengl3.cpp is not an exception. Vertex offset is relatively new thing in ImGui. Implementing it is possible in any OpenGL version (different extensions, different hoops to overcome). The thing is, nobody contributed such change yet.

What can be done:

  • You can consider adding support for your version of OpenGL
  • You can use ImDrawData::DeIndexAllBuffers() to flatten buffers, which eliminate indexing.
    This still require a patch for the backend, because most of them expect indexing. There will also be a performance hit.

I would advocate for you to stay with ImGui and help to push it forward by contributing. But that's me. Choice is yours. :)

commented

Or just use #define ImDrawIdx unsigned int... I don't know why not using this.

Hello @ocornut, thanks for your answer.

I tried using
#define ImDrawIdx unsigned int

And it works fine in my desktop version, but not for WebAssembly (Emscripten), that is the problem.

@thedmd, also thanks for your answer, If I find the way to add support to my OpenGL version I will be glad to contribute to the project.

Now I have the scenario crystal clear. Txs to both!

commented

And it works fine in my desktop version, but not for WebAssembly (Emscripten), that is the problem.

It should work in WebAssembly, you should investigate the cause of the issue. Are you sure you added the define in imconfig.h ?

commented

Never mind, I realize that the glDrawElements() call will compile but WebGL specs do not allow 16-bit indices.

The simplest (but slow) solution is to Deindex the buffer, otherwise your plotting code needs to create new drawlists to avoid going over 64k.

Hello Omar, I studied a little bit of OpenGL and WebGL, and now I have a little WebGL program to plot more than 1.5MM of points in a single scene. My application is plotting seismic signals, so I tested with a test file that has 64K points per geophone, and 24 geophones, that is 64.000 * 24 = 1.536.000 points (24 linestrips)
I used 24 buffer objects, and plotting avoiding the use of an index (glDrawArrays instead of glDrawElements)
I am just writting to you because perhaps you may find the code usefull.
Here is the WebAssembly test application (loading the test file is slow, you can see the progress in the JS console log), you can pan using the arrow keys (I am not using colors), and zoom in using "+" (it is not perfect, is just to display big vertex sets):
My WebGL Little test
In my Core i7 I have to wait 10 secs aprox to see the image. Then I see it with a fast rendering while zooming/panning.
If you think this might be usefull to the project, please let me know how could I send you the code as a tiny contribution.
Another Question: I want to modify ImGUI/ImPlot for this purpose, is there any problem with it?

I think I will have to define a class similar to ImDrawList but only for the geophone data (that is like having more than one draw list per window), With no indexes, just for use with glDrawArrays. I don't need Indexes, because the reuse of vertex data is minimal (render each geophone data is always a line_strip or a triangle_strip, and if a vertex belongs to a signal, it will never be used for render another signal).
Then, inside struct IMGUI_API ImGuiWindow, Keep ImDrawList DrawListInst without modifications to avoid problems with the functionalities ImGui already have working fine (and DrawListInst is used everywhere).
Define a container, perhaps a map<string, ImDrawList_Like_class*> to store all geophones data that must be rendered.
Modify Implot to render the signals using the defined map.
I think this can be useful for the project when you need to draw very high volumes of data even for the web.