LOADING

加载过慢请开启缓存 浏览器默认开启

Imgui Docking

笔记14.Imgui Docking

他承认了确实将imgui_glfw的功能大部分放入了Imgui_Layer中实现了。

之前在imgui的笔记中也说过,cherno将渲染层opengl的imgui文件自己命名ImguiOpenGLRenderer,同时将glfw3的实现抽出来放在了imguiLayer中的文件中。

但是这样自己维护和功能的更新就带来了麻烦


  1. 虽然视频更新了拉取的Docking分支库,但是最新的imgui显然整合了这个功能,所以不太需要追踪分支。

  2. 在此之前还是说一下imgui的使用。

我们在文档中看到使用方法

Example: If you are using GLFW + OpenGL/WebGL 示例:如果您使用的是 GLFW + OpenGL/WebGL

Full standalone example: example_glfw_opengl3/main.cpp
完整独立示例:example_glfw_opengl3/main.cpp

Add to Includes: 添加到包含:

#include "imgui.h"
#include "imgui_impl_glfw.h"
#include "imgui_impl_opengl3.h"

Add to Initialization: 添加到初始化:

// Setup Dear ImGui context
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;     // Enable Keyboard Controls
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;      // Enable Gamepad Controls
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;         // IF using Docking Branch

// Setup Platform/Renderer backends
ImGui_ImplGlfw_InitForOpenGL(YOUR_WINDOW, true);          // Second param install_callback=true will install GLFW callbacks and chain to existing ones.
ImGui_ImplOpenGL3_Init();

Add to start of main loop:
添加到 main 循环的开头:

// (Your code calls glfwPollEvents())
// ...
// Start the Dear ImGui frame
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
ImGui::ShowDemoWindow(); // Show demo window! :)

Add to end of main loop:
添加到 main 循环的末尾:

// Rendering
// (Your code clears your framebuffer, renders your other stuff etc.)
ImGui::Render();
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
// (Your code calls glfwSwapBuffers() etc.)

Add to Shutdown: 添加到关机:

ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplGlfw_Shutdown();
ImGui::DestroyContext();

That should be all! 这应该就是全部!

  • 我们先解释一下头文件吧

imgui.h 是 ImGui 的核心框架,提供了通用的 UI 逻辑,提供了逻辑的抽象层,具体的需要在对应的后端cpp中进行实现。

"imgui_impl_glfw.h" 是与操作系统相关的输入和窗口管理实现,让 ImGui 能够响应用户输入和处理窗口事件,就是将glfw的对设备输入的检查函数与imgui中的回调函数函数挂钩,让imgui能够接收到对应的事件。

但是我们要注意使用这个头文件的前提,是你引入了glfw的库,我们在imgui_impl_glfw.cpp中可以看到需要你按照他的路径方式来导入glfw库,这样才能调用glfw的库函数。

image-20241126172750986

imgui_impl_openGL.h 使用 OpenGL 渲染 API,将 UI 绘制数据翻译成实际的图形指令并渲染到屏幕上。

要注意imgui又是如何加载openGL的函数地址呢,我们可以看到(默认没有定义IMGUI_IMPL_OPENGL_LOADER_CUSTOM,只不过是我定义的),在imgui_impl_openGL.cpp 文件中引入了imgui_impl_opengl3_loader.h,点进去阅读文档,你会发现就是一个小型的opengl函数地址加载器。

和你直接引入<glad/glad.h>的区别就在于:

  • loader会引入imgui中需要的函数地址,而不会多余的加载,从而实现了小体量,关于loader的具体方式,是使用脚本什么的来加载,较为复杂,可以自己参阅源代码中Loader的gl3w的实现库去。

另外补充一点,他怎么知道你使用的glad还是glew来加载openGL的?

因为OpenGL是规范,各个库只是加载函数的方式不同,但是函数名称和接口规范都是大体一致的,所以不管你使用哪一个,他只要使用规范的OpenGL函数就可以实现渲染逻辑。

image-20241126173015497

完整工作流

  • imgui_impl_glfw 收集用户输入(鼠标、键盘)并将其反馈给核心框架。
  • imgui_impl_dx12 根据核心框架生成的绘制数据,将 UI 渲染到屏幕。

  • TO DO

主循环的逻辑部分到时候,就是上面的示例。


  1. 我们先把前面的头文件的实现都放在一个翻译单元(CPP文件)

所以我们呢创建了ImGUiBuild.cpp

#include "hzpch.h"


#include "backends/imgui_impl_glfw.cpp"
#include "backends/imgui_impl_opengl3.cpp"

各自的cpp文件自带头文件,这样所有函数都实现了,之后我们只需要的时候只要引入对应的头文件就好了。


  1. 首先删除了OpenGL文件夹下的文件(因为已经有了现成的,在build中包含)
  2. 然后大体重写了imguiLayer的结构
//imguiLayer.h
class  ImGuiLayer: public Layer
{
public:
    ImGuiLayer();
    ~ImGuiLayer();


    virtual void OnAttach() override;			//创建imgui所需的初始化操作
    virtual void OnDetach() override;			//删除imgui的卸载资源等操作
    virtual void OnImGuiRender()override;		//渲染所需内容

    void Begin();								//每一帧渲染前的初始化
    void End();									//处理所有渲染数据并提交渲染数据,更新渲染所有窗口

private:
    float m_Time = 0.0f;
};

//imguiLayer.cpp
ImGuiLayer::ImGuiLayer()
{

}

ImGuiLayer::~ImGuiLayer()
{

}

void ImGuiLayer::OnAttach()
{
    //Setup Dear ImGui context
    IMGUI_CHECKVERSION();
    ImGui::CreateContext();
    ImGuiIO& io = ImGui::GetIO();
    io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;		//Enable Keyboards Controls
    //io.configFlags |=ImGuiConfigFlags_NavEnableGamepad;		//Enable Gamepad Controls
    io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;			//Enable Docking
    io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;			//Enable Multi-Viewport / Platform Windows
    //io.ConfigFlags |= ImGuiConfigFlags_ViewportsNoTaskBarIcons;
    //io.ConfigFlags |= ImGuiConfigFlags_ViewportsNomerge;	

    //Setup Dear ImGui style
    ImGui::StyleColorsDark();
    //ImGui::StyleColorClassic();

    //When viewports are enabled we tweak windowsRounding/WindowBg so platform windows can look identical
    ImGuiStyle& style = ImGui::GetStyle();
    if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
    {
        style.WindowRounding = 0.0f;
        style.Colors[ImGuiCol_WindowBg].w = 1.0f;
    }

    Application& app = Application::Get();
    GLFWwindow* window = static_cast<GLFWwindow*>(app.GetWindow().GetNativeWindow());

    //Setup platform/Renderer bindings



    ImGui_ImplGlfw_InitForOpenGL(window, true);
    ImGui_ImplOpenGL3_Init();
}

void ImGuiLayer::OnDetach()
{
    ImGui_ImplOpenGL3_Shutdown();
    ImGui_ImplGlfw_Shutdown();
    ImGui::DestroyContext();

}

void ImGuiLayer::OnImGuiRender()
{
    static bool show = true;
    ImGui::ShowDemoWindow(&show);
}

void ImGuiLayer::Begin()
{
    //它们分别处理 OpenGL 和 GLFW 的新帧初始化,例如处理输入、刷新状态等。
    ImGui_ImplOpenGL3_NewFrame();
    ImGui_ImplGlfw_NewFrame();
    //告诉 ImGui 开始一帧新的绘制操作。所有的 UI 绘制操作都应该在 NewFrame 和 Render 之间完成。
    ImGui::NewFrame();
}

void ImGuiLayer::End()
{
    ImGuiIO& io = ImGui::GetIO();
    Application& app = Application::Get();

    io.DisplaySize = ImVec2((float)app.GetWindow().GetWidth(), (float)app.GetWindow().GetHeight());

    //Rendering
    ImGui::Render();
    ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());


    if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
    {
        //由于每个窗口可能有自己的 OpenGL 上下文,所以需要使用 glfwMakeContextCurrent() 恢复之前的上下文。
        GLFWwindow* backup_current_context = glfwGetCurrentContext();
        ImGui::UpdatePlatformWindows();
        ImGui::RenderPlatformWindowsDefault();
        glfwMakeContextCurrent(backup_current_context);
    }
}
image-20241130145831086
  1. 调试imgui显然属于引擎层面的,我们放入Application.cpp中实现渲染,依旧是layer的放入和使用方法。

但是又如下的修改。

  • layerstack的对应layer获取放弃了迭代器选择了索引计算位置。
void LayerStack::PushLayer(Layer* layer)
{
    //m_LayerInsertIndex是Layer中前半层和后半层的中间
    m_Layers.emplace(m_Layers.begin()+m_LayerInsertIndex, layer);
    m_LayerInsertIndex++;
}

为什么数组的迭代器能够与unsigned int直接相加计算到迭代器地址?

因为内部实现了迭代器和整数相机的重载,从而可以计算对应的偏移量,获得对应位置的指针。

  • 原本使用uniqe_ptr指针存储imguiLayer,中途换成了普通指针储存

因为layer统一需要放进layerstack中进行管理,而layerstack却又是vector数组实现的

上面看到,存放是通过emplace实现的。

std::unique_ptr 是一种智能指针,它独占其所指向的资源的所有权,因此 std::unique_ptr 是不可复制的,拷贝构造和拷贝赋值操作被禁用,但是支持移动构造和移动赋值,允许资源从一个 std::unique_ptr 转移到另一个 std::unique_ptr

emplace容器的内存区域直接构造对象(原地构造),而不是插入一个现有的对象。

移动可以通过 emplace_back(std::move(ptr)) 明确实现,避免语义不清

所以最后使用了普通指针存放imguiLayer。

image-20241130151648277