Jump to content

ImGui - Integration


klepto2
 Share

Recommended Posts

I already posted a small screenshot in the Gallery showing an early proof of concept for integrating ImGui into UltraEngine.

https://www.ultraengine.com/community/gallery/image/2676-imgui-integration-in-ultraengine/

Now I have made some progress and finished a lot of the integration stuff.

  1. Ordered Rendering (not completely working, sometimes the order seems a bit off)
  2. Keyboard and MouseInput 
  3. Shader and Material stuff
  4. General-Rendering is working

What is still to do:

  1.  Resolve some clipping errors
  2.  Make the whole integration more Ultra-like ;) not the c approach ImGui is normally using for initialisation
  3. Add multiple texture and font support (for images or framebuffer display)

 

This small gif shows a small sample with the provided demo window and a custom debug window showing the renderstats:

image.thumb.gif.2fcef9573096227c5db37ef0c75204a3.gif

Here is a small code snippet showing the debug window code:

#define MAX_FRAMES 100

struct StatCounter
{
    float data[MAX_FRAMES];
    float getMax() { return *max_element(data, data + MAX_FRAMES); }
    float getAvarge()  
    {
        float average = 0.0f;
        int removeCounts = 0;
        for (int n = 0; n < IM_ARRAYSIZE(data); n++)
        {
            auto v = data[n];
            if (v < 0.0) {
                v = 0.0;
                removeCounts++;
            }
            average += v;
        }
        average /= (float)(MAX_FRAMES - removeCounts);

        return average;
    }
};

class ImDebugRenderer : public Object
{
    shared_ptr<World> _world;
    StatCounter _fpsCounter;
    StatCounter _cullCounter;
    StatCounter _renderCounter;
    int _currentFrame = 0;
    bool _isOpen = true;

public:
    ImDebugRenderer(shared_ptr<World> world) : _world(world)
    {
        _world->RecordStats(true);
    }

    void CollectStatistics()
    {
        _fpsCounter.data[_currentFrame] = _world->renderstats.framerate;
        _cullCounter.data[_currentFrame] = _world->renderstats.cullingtime;
        _renderCounter.data[_currentFrame] = _world->renderstats.rendertime;
    }

    void Render()
    {
        CollectStatistics();

        if (_isOpen)
        {
            ImGui::SetNextWindowPos(ImVec2(20, 20), ImGuiCond_Appearing);
            ImGui::SetNextWindowBgAlpha(0.5);
            //ImGui::SetNextWindowSize(ImVec2(350, 420), ImGuiCond_Appearing);

            ImGui::Begin("Debug", &_isOpen, ImGuiWindowFlags_AlwaysAutoResize);

            ImGui::PlotLines(("Framerate (" + String(_world->renderstats.framerate) + " fps )").c_str(), _fpsCounter.data, IM_ARRAYSIZE(_fpsCounter.data), _currentFrame, ("average : " + String(_fpsCounter.getAvarge())).c_str(), 0.0, _fpsCounter.getMax(), ImVec2(0, 80.0f));
            ImGui::PlotLines(("Culltime (" + String(_world->renderstats.cullingtime) + " ms)").c_str(), _cullCounter.data, IM_ARRAYSIZE(_cullCounter.data), _currentFrame, ("average : " + String(_cullCounter.getAvarge())).c_str(), 0.0, _cullCounter.getMax(), ImVec2(0, 80.0f));
            ImGui::PlotLines(("Rendertime (" + String(_world->renderstats.rendertime) + " ms)").c_str(), _renderCounter.data, IM_ARRAYSIZE(_renderCounter.data), _currentFrame, ("average : " + String(_renderCounter.getAvarge())).c_str(), 0.0, _renderCounter.getMax(), ImVec2(0, 80.0f));

            ImGui::Text("Cameras       : %d", _world->renderstats.cameras);
            ImGui::Text("Meshbatches   : %d", _world->renderstats.meshbatches);
            ImGui::Text("Pipelines     : %d", _world->renderstats.pipelines);
            ImGui::Text("Polygons      : %d", _world->renderstats.polygons);
            ImGui::Text("Vertices      : %d", _world->renderstats.vertices);
            ImGui::Text("Instances     : %d", _world->renderstats.instances);
            ImGui::Text("Shadows       : %d", _world->renderstats.shadows);
            ImGui::Text("Shadowpolygons: %d", _world->renderstats.shadowpolygons);
            ImGui::Text("VRam          : %d", _world->renderstats.vram);

            ImGui::End();
        }

        _currentFrame = (_currentFrame + 1) % MAX_FRAMES;
    }

    void Show() { _isOpen = true; };
};

The setup is more or less the same as for the default GUI, you need an orthographic camera and a separate renderlayer. But then you can simply call:

 

// This is how i imagine a more ultra-way of rendering the ImGui
auto ui_manager = ImGuiUltraManager::Create(window, world, renderlayer); //Framebuffer will work as well

void main()
{
	ui_manager->BeginFrame(); // Begins the frame-recording
	debugWindow->Render(); // Add the commands to draw the debug window (code from above)
	ui_manager->Render(); // Stops the frame-recording and prepares the models for rendering

	world->Update();
	world->Render(framebuffer,true);
}

 

 

  • Like 2
  • 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

small update: 

clipping errors and z-order problems are fixed.

Now working on the simplyfied (ultralike) access and setting up a github repo for this to give access to this gem to everyone.

  • 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

Made a lot of progress today :)

I have finished the basic ImGuiManager for UltraEngine (not yet commited) with some additional features: 

  • you can now register your own UltraEngine Textures to be used by ImGui
    • ImGuiManager::RegisterTexture(texture) will return an ImTextureID which can be used then by eg: ImGui::Image(...)
  • Multiple-Fonts are now supported + reload and recreation of the texture-atlas

This is a small sample which i am using to test some features:

#include "UltraEngine.h"
#include "Components/Motion/Mover.hpp"
#include "imgui-integration/ImGuiManager.h"

using namespace UltraEngine;
using namespace UltraEngine::ImGuiIntegration;
using namespace std;

#define MAX_FRAMES 100


struct StatCounter
{
    float data[MAX_FRAMES];
    float getMax() { return *max_element(data, data + MAX_FRAMES); }
    float getAvarge()
    {
        float average = 0.0f;
        int removeCounts = 0;
        for (int n = 0; n < IM_ARRAYSIZE(data); n++)
        {
            auto v = data[n];
            if (v < 0.0) {
                v = 0.0;
                removeCounts++;
            }
            average += v;
        }
        average /= (float)(MAX_FRAMES - removeCounts);

        return average;
    }
};

class ImDebugRenderer : public Object
{
    shared_ptr<World> _world;
    StatCounter _fpsCounter;
    StatCounter _cullCounter;
    StatCounter _renderCounter;
    int _currentFrame = 0;
    bool _isOpen = true;

public:
    ImDebugRenderer(shared_ptr<World> world) : _world(world)
    {
        _world->RecordStats(true);
    }

    void CollectStatistics()
    {
        _fpsCounter.data[_currentFrame] = _world->renderstats.framerate;
        _cullCounter.data[_currentFrame] = _world->renderstats.cullingtime;
        _renderCounter.data[_currentFrame] = _world->renderstats.rendertime;
    }

    void Render()
    {
        CollectStatistics();

        if (_isOpen)
        {
            ImGui::SetNextWindowPos(ImVec2(20, 20), ImGuiCond_Appearing);
            ImGui::SetNextWindowBgAlpha(0.5);
            //ImGui::SetNextWindowSize(ImVec2(350, 420), ImGuiCond_Appearing);

            ImGui::Begin("Debug", &_isOpen, ImGuiWindowFlags_AlwaysAutoResize);

            ImGui::PlotLines(("Framerate (" + String(_world->renderstats.framerate) + " fps )").c_str(), _fpsCounter.data, IM_ARRAYSIZE(_fpsCounter.data), _currentFrame, ("average : " + String(_fpsCounter.getAvarge())).c_str(), 0.0, _fpsCounter.getMax(), ImVec2(0, 80.0f));
            ImGui::PlotLines(("Culltime (" + String(_world->renderstats.cullingtime) + " ms)").c_str(), _cullCounter.data, IM_ARRAYSIZE(_cullCounter.data), _currentFrame, ("average : " + String(_cullCounter.getAvarge())).c_str(), 0.0, _cullCounter.getMax(), ImVec2(0, 80.0f));
            ImGui::PlotLines(("Rendertime (" + String(_world->renderstats.rendertime) + " ms)").c_str(), _renderCounter.data, IM_ARRAYSIZE(_renderCounter.data), _currentFrame, ("average : " + String(_renderCounter.getAvarge())).c_str(), 0.0, _renderCounter.getMax(), ImVec2(0, 80.0f));

            ImGui::Text("Cameras       : %d", _world->renderstats.cameras);
            ImGui::Text("Meshbatches   : %d", _world->renderstats.meshbatches);
            ImGui::Text("Pipelines     : %d", _world->renderstats.pipelines);
            ImGui::Text("Polygons      : %d", _world->renderstats.polygons);
            ImGui::Text("Vertices      : %d", _world->renderstats.vertices);
            ImGui::Text("Instances     : %d", _world->renderstats.instances);
            ImGui::Text("Shadows       : %d", _world->renderstats.shadows);
            ImGui::Text("Shadowpolygons: %d", _world->renderstats.shadowpolygons);
            ImGui::Text("VRam          : %d", _world->renderstats.vram);

            ImGui::End();
        }

        _currentFrame = (_currentFrame + 1) % MAX_FRAMES;
    }

    void Show() { _isOpen = true; };

    void Toggle() { _isOpen = !_isOpen; };
};

int main(int argc, const char* argv[])
{
    auto plg = LoadPlugin("Plugins/FITextureLoader");
    auto plg2 = LoadPlugin("Plugins/KTX2TextureLoader");
    //Get the displays
    auto displays = GetDisplays();

    //Create a window
    auto window = CreateWindow("Ultra Engine", 0, 0, 1280, 720, displays[0], WINDOW_CENTER | WINDOW_TITLEBAR);

    //Create a world
    auto world = CreateWorld();

    //Create a framebuffer
    auto framebuffer = CreateFramebuffer(window);

    //Create a camera
    auto camera = CreateCamera(world);
    camera->SetPosition(0, 0, -1);
    camera->SetFov(70);
    camera->SetClearColor(0.125);
    camera->SetTessellation(4);

    //Create a light
    auto  light = CreateBoxLight(world);
    light->SetRange(-10, 10);
    light->SetRotation(35, 35, 0);
    light->SetColor(4);


    //Display material
    auto model = CreateCubeSphere(world, 0.5, 8, MESH_QUADS);
    auto mtl = LoadMaterial("Materials/rocks_ground_02.json");
    mtl->SetTessellation(true);
    mtl->SetDisplacement(0.075f);
    model->SetMaterial(mtl);

    //Entity component system
    auto component = model->AddComponent<Mover>();
    component->rotationspeed.y = 45;

    // Init the Manager
    auto imGuiManager = CreateImGuiManager(window, world);
    auto debugRenderer = make_shared<ImDebugRenderer>(world); // A custom class to render debug information for the world

    auto sampleTexture = mtl->GetTexture(0); // Get the diffuse Texture
    auto imGuiTexture = imGuiManager->RegisterTexture(sampleTexture); // Register the texture for usage with ImGui

    //Define some settings to control the camera and material settings
    bool camera_wireFrame = false;
    float tesselation_level = 4.0;
    Vec2 mtl_displacement = mtl->GetDisplacement();

    //Main loop
    while (window->Closed() == false and window->KeyDown(KEY_ESCAPE) == false)
    {
        //Begin the ui-definition
        imGuiManager->BeginFrame();
        debugRenderer->Render(); //render the debug panel (only when open)

        //Start rendering of the Settings
        ImGui::Begin("Settings", NULL, ImGuiWindowFlags_AlwaysAutoResize);
        ImGui::Checkbox("Wireframe", &camera_wireFrame);
        ImGui::SliderFloat("Tesselation-Level", &tesselation_level,0.0,32);
        ImGui::SliderFloat2("Displacement", &mtl_displacement[0], -1.0, 1.0);
        if (ImGui::Button("Toggle Debug-Window"))
        {
            debugRenderer->Toggle();
        }
        ImGui::End();

        //Render a window containing the UltraEngine texture
        ImGui::Begin("Texture", NULL, ImGuiWindowFlags_AlwaysAutoResize);
        ImGui::Image(imGuiTexture,ImVec2(128,128));
        ImGui::End();

        //Stop UI-Recording and update/create the models for UltraEngine
        imGuiManager->Sync();

        //Update the Settings modified by the UI
        camera->SetWireframe(camera_wireFrame);
        camera->SetTessellation(tesselation_level);
        mtl->SetDisplacement(mtl_displacement.x, mtl_displacement.y);

        world->Update();
        world->Render(framebuffer);
    }
    return 0;
}

imGui_ultra_engine_2.thumb.gif.8a8ee5ee11269a916fdf58fa9e3bbd4b.gif

 

  • Like 3
  • 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

Next Step:

Finished the update to the "Dear ImGui"  "docking"-branch. The docking branch is a stable dev branch which contains the latest features as docking or multi-viewport systems. Docking is self explaining and I show it in the image below. Essentially multi-viewport means, that you can move windows out of your application, i haven't tested that (new windows will be generated, etc.)

image.thumb.png.8ae24e00e59b07234d220af6fd8b5ba7.png

image.thumb.png.723ca8832a6926f424b997861655615e.png

Other remarkable milestones:

  • Rendering is more or less optimized. (With some dirty hacks ;))
    • I have refactored the Model/Mesh generation and updating method and the main loop still updates with 60fps even with high complex ui's
  • Multiple Fonts rendering is also possible 
  • Texture-System is in place.

What needs to be done:

  • Replace the current Win32 driver with a native UltraEngine way
  • Add feature to allow render to texture and proper Input handling in this case
  • and of course optimization
  • Like 3
  • 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

One small addition: While this Ui-System is mainly for prototyping or debugging/dev things. It might come in handy, that it has a lot of lowlevel drawing functions. Which means, that when i have done the Rendertarget implementation, it could be possible to draw all kinds of 2d shapes, etc into a texture, which can then be rendered into a UltraEngine::Widget. Or you can combine these things and use the Widget system and ImGui together, e.g.:  for something like this: https://github.com/thedmd/imgui-node-editor

  • 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

Although IMgui is using immediate mode to render, you still have an advantage with the Ultra architecture because all the mesh generation code is executing in the game logic thread (I assume). That means the only rendering cost is the bandwidth of uploading the updated mesh to the GPU, and it sounds like that mesh data is not huge.

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

On 9/8/2023 at 5:57 PM, Josh said:

Although IMgui is using immediate mode to render, you still have an advantage with the Ultra architecture because all the mesh generation code is executing in the game logic thread (I assume). That means the only rendering cost is the bandwidth of uploading the updated mesh to the GPU, and it sounds like that mesh data is not huge.

You*re completely right. And with the new updates, it is even better :) The only thing is that in debug mode the culling takes a lot of time (10 - 13ms at average), which leads to a lower update even only one mesh is on screen. I remember that @SpiderPig has made the suggestion, to be able to disable the culling for certain cameras. And for orthographic cameras i fully agree with him. Normally in a 2d space it could be leed to the programmer, what to render and what not.

Here are some progress screens of the current Statistics panel i have done with Dear ImGui:

image.thumb.png.6320b665c6c1a4326fa35df48a279997.png

image.thumb.png.b20f20fe6253b209f49e9c1b52b5e17f.png

image.thumb.png.92c92dedf559a56f7637e0b13bb4d5e9.png

image.thumb.png.9e5da8cb73c9c966f562f52023c3cdbb.png

  • Like 2
  • 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

  • 3 months later...

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...