从实现一个ImGui后端谈起:立即UI和bindless
发布于:
最近花了大概四天摸鱼时间,在自己的渲染器里接入了imgui 本来应该要不了这么久,但是我决定做一些新鲜的花样…比如说 一个drawcall一次性画出所有的ui! 成品图:  # ImGui的架构 在下载imgui的时候其实就能发现了,有一部分文件直接存在于ImGui文件夹里,另一部分存在于backend文件夹里 我觉得imgui可以粗暴的分为两部分:前端和后端 前端负责widget的排版,组装为顶点;而后端负责把组装好的信息渲染出来。 但更进一步的,我们也可以发现这些后端实现的名字似乎是分为两大类,一些是图形api的名字,比如 `imgui_impl_dx11.h`,另一部分则是平台的名字,比如`imgui_impl_android.h`。这是因为ui需要得到用户的输入,就必须和窗口系统打交道。所以,这里又分为了io后端,和渲染后端。 要在引擎中接入imgui,这两部分我们都得实现。 我这篇文章也主要是着眼于一个imgui后端实现。 ## 前端  正如我所说前端负责组织IMGUI的排版和组装顶点。 不管什么widget的函数,最后的终点都会是ImDrawList::AddXXXXX,比如ImDrawList::AddLine, ImDrawList::AddRect,ImDrawList::AddQuad..... 也就是说所有的控件都是由ImDrawList用点线面等基础元素组织起来的,而这些函数的参数一般特别的简单,就是几个ImVec2,这些顶点最后的目的地是` ImVector<ImDrawIdx> IdxBuffer;`和`ImVector<ImDrawVert> VtxBuffer;`。 所以我说ImGui就是点线面组成的拼好UI,不过分吧? ## 前端和后端的交互 ImGui的前端和后端的分界线在ImGui::Render这个函数上。 因为这个函数之后,我们就能通过`ImGui::GetDrawData()`拿到用于渲染ImGui所要的数据,这个数据会在之后被送入ImGui的后端进行绘制。 ImDrawData张这个样子: ``` struct ImDrawData { bool Valid; // Only valid after Render() is called and before the next NewFrame() is called. int CmdListsCount; // == CmdLists.Size. (OBSOLETE: exists for legacy reasons). Number of ImDrawList* to render. int TotalIdxCount; // For convenience, sum of all ImDrawList's IdxBuffer.Size int TotalVtxCount; // For convenience, sum of all ImDrawList's VtxBuffer.Size ImVector<ImDrawList*> CmdLists; // Array of ImDrawList* to render. The ImDrawLists are owned by ImGuiContext and only pointed to from here. ImVec2 DisplayPos; // Top-left position of the viewport to render (== top-left of the orthogonal projection matrix to use) (== GetMainViewport()->Pos for the main viewport, == (0.0) in most single-viewport applications) ImVec2 DisplaySize; // Size of the viewport to render (== GetMainViewport()->Size for the main viewport, == io.DisplaySize in most single-viewport applications) ImVec2 FramebufferScale; // Amount of pixels for each unit of DisplaySize. Copied from viewport->FramebufferScale (== io.DisplayFramebufferScale for main viewport). Generally (1,1) on normal display, (2,2) on OSX with Retina display. ImGuiViewport* OwnerViewport; // Viewport carrying the ImDrawData instance, might be of use to the renderer (generally not). ImVector<ImTextureData*>* Textures; // List of textures to update. Most of the times the list is shared by all ImDrawData, has only 1 texture and it doesn't need any update. This almost always points to ImGui::GetPlatformIO().Textures[]. May be overridden or set to NULL if you want to manually update textures. // Functions ImDrawData() { Clear(); } IMGUI_API void Clear(); IMGUI_API void AddDrawList(ImDrawList* draw_list); // Helper to add an external draw list into an existing ImDrawData. IMGUI_API void DeIndexAllBuffers(); // Helper to convert all buffers from indexed to non-indexed, in case you cannot render indexed. Note: this is slow and most likely a waste of resources. Always prefer indexed rendering! IMGUI_API void ScaleClipRects(const ImVec2& fb_scale); // Helper to scale the ClipRect field of each ImDrawCmd. Use if your final output buffer is at a different scale than Dear ImGui expects, or if there is a difference between your window resolution and framebuffer resolution. }; ``` 主要的渲染命令都存在于CmdLists里,CmdList长这样 ```cpp struct ImDrawList { // This is what you have to render ImVector<ImDrawCmd> CmdBuffer; // Draw commands. Typically 1 command = 1 GPU draw call, unless the command is a callback. ImVector<ImDrawIdx> IdxBuffer; // Index buffer. Each command consume ImDrawCmd::ElemCount of those ImVector<ImDrawVert> VtxBuffer; // Vertex buffer. ImDrawListFlags Flags; // Flags, you may poke into these to adjust anti-aliasing settings per-primitive. //Omit others..... } ``` 里面记录了一系列ImDrawCmd公用的Vertex和Index 接着去看ImDrawCmd ``` struct ImDrawCmd { ImVec4 ClipRect; // 4*4 // Clipping rectangle (x1, y1, x2, y2). Subtract ImDrawData->DisplayPos to get clipping rectangle in "viewport" coordinates ImTextureRef TexRef; // 16 // Reference to a font/texture atlas (where backend called ImTextureData::SetTexID()) or to a user-provided texture ID (via e.g. ImGui::Image() calls). Both will lead to a ImTextureID value. unsigned int VtxOffset; // 4 // Start offset in vertex buffer. ImGuiBackendFlags_RendererHasVtxOffset: always 0, otherwise may be >0 to support meshes larger than 64K vertices with 16-bit indices. unsigned int IdxOffset; // 4 // Start offset in index buffer. unsigned int ElemCount; // 4 // Number of indices (multiple of 3) to be rendered as triangles. Vertices are stored in the callee ImDrawList's vtx_buffer[] array, indices in idx_buffer[]. ImDrawCallback UserCallback; // 4-8 // If != NULL, call the function instead of rendering the vertices. clip_rect and texture_id will be set normally. void* UserCallbackData; // 4-8 // Callback user data (when UserCallback != NULL). If called AddCallback() with size == 0, this is a copy of the AddCallback() argument. If called AddCallback() with size > 0, this is pointing to a buffer where data is stored. int UserCallbackDataSize; // 4 // Size of callback user data when using storage, otherwise 0. int UserCallbackDataOffset;// 4 // [Internal] Offset of callback user data when using storage, otherwise -1. ImDrawCmd() { memset((void*)this, 0, sizeof(*this)); } // Also ensure our padding fields are zeroed // Since 1.83: returns ImTextureID associated with this draw call. Warning: DO NOT assume this is always same as 'TextureId' (we will change this function for an upcoming feature) // Since 1.92: removed ImDrawCmd::TextureId field, the getter function must be used! inline ImTextureID GetTexID() const; // == (TexRef._TexData ? TexRef._TexData->TexID : TexRef._TexID) }; ``` 里面定义了一次绘制对应的VtxOffset,IdxOffset,和index个数,贴图ID。 现在对我而言,一个比较有意思的问题是,什么情况IMGUi会主动去增加一个ImDrawList,又是什么情况会增加ImDrawCmd? 先说ImDrawCmd,这部分其实比较好猜,因为看了一下官方backend里的Shader/根据Cmd的结构,可以发现: 一个Shader只有一个贴图槽位,也就是说如果有多个不同贴图,但是他们公用一个VertexList和IndexList,那么就会被打包成一个DrawList里的不同Cmd 还有一点特别隐蔽的是,ImGui实际上是靠着图形API的某种Scissor来裁剪掉超出Window范围的图形, 这个Scissor存在ImDrawCmd::ClipRect里,而Scissor是Per-Cmd生效的(参考VkCmdSetScissor)。 由此可以得出结论ImGui会把具有 *相同贴图*且拥有*相同ClipRect*打包成一个CmdDraw. > 笑话一则:当我以为已经完成了backend的时候,突然发现---怎么有几个字露出来了.....为什么Window外面有个图片!!! ImDrawList增加的原因就很简单了,每个ChildWindow/Window都会产生一个ImDrawList。 还有一种特殊情况,因为ImGUI默认的IndexType是u16,所以如果顶点太多了,也会被强制拆分为几个DrawList。 ## 所以,一个Immediate UI? ImGUI的做法是,每次调用Render的时候,会从头组装所有的渲染资源,包括所有的Vertex,所有的Index,每个按钮是否被点击了也只是一个暂时的状态,它的内部不会储存任何留存超过一帧的信息,一切都是立即被构建,立即被处理,立即被渲染。 不管每帧的控件如何,我都会重新申请内存,重新填充数据。 这样做最大的问题就是:CPU上的性能会非常的差 对于一个Retain UI系统,我可能会先申请一大段显存作为顶点Buffer,每个控件会从这里要一段内存,存自己的顶点,有且只有这个控件被修改了,或者这颗控件树脏了,它的数据才会被重新构建。控件内部会保存自己的数据,知道自己当前的状态。 这样可能Drawcall会比较多,但是CPU上的开销会很低。 # 实现一个ImGui后端 根据上面的分析,我们可以得到最最简单的ImGui后端做法: 对于每个DrawList创建一个VtxBuffer,一个IndexBuffer,对每个cmdDraw去绑定贴图,绑定VertexOffset,IndexOffset,应用Scissor,然后dispatch drawcall ------------------------------------------------------ 那有没有方法优化下这套流程呢? 其实借助bindless系统,完全可以做到一次Drawcall画出所有UI ## Bindless 其实最早知道Bindless是从[WickedEngine的DevBlog](https://turanszkij.wordpress.com/2023/11/16/dynamic-vertex-formats/)里看到的,里面介绍了一种可以免去做InputVertexAttribute,达到动态VertexFormat的方法。 Shader的布局中只用留下这些: ```cpp struct ShaderGeometry // part of a mesh, this structure can be shared with CPU code { int vb_pos_nor_wind; int vb_col; // ... }; ConstantBuffer<ShaderGeomtry> geometry; StructuredBuffer<float4> bindless_buffers_structured_float4[]; Buffer<float4> bindless_buffers_float4[]; ``` 一个通用的用于传递必要信息的constantBuffer,两个通用的不定长StructuredBufferArray 渲染顶点的数量是在DispatchDrawcall的时候决定的。 在渲染的时候,从ConstantBuffer中得到索引,动态的去几个不定长Buffer里获取到这次渲染需要的Buffer,并且索引到顶点 ```cpp StructuredBuffer<float4> buffer_pos_nor_wind = bindless_buffers_structured_float4[geometry.vb_pos_nor_wind]; float4 pos_nor_wind = buffer_pos_nor_wind[vertexID]; ``` 对于贴图也可以用这种方式进行动态索引,一个用来存信息的constantbuffer(通常存的是这个贴图的索引),一个用来存贴图的不定长的数组... Bindless的好处就是非常大增加Shader的灵活性,减少需要的Shader变体,减少绑定这个操作带来的api开销 可以用同样的思路去绘制ImGui 在ImGui中,切分ImCmd的原因是贴图和Scissor,把这两个东西打包进一个AttributeBuffer里,渲染的时候动态寻找。 再建立一个PerTriangle的Buffer,用来存放当前Triangle到其ImCmdDraw的Attribute的索引,每次渲染的时候动态取出贴图和Scissor > 至于IndexBufer和VertexBuffer要不要做成Bindless反而可有可无了 ### Bindless Buffer?A better choice 我相信至少有人好奇过,为什么我们不能像写CPP一样写shader,就好比buffer这个资源,在CPU上Buffer就是一段内存,我们可以通过一个地址+偏移量访问 在主流图形API中也有显存Buffer,但是我们得到的是一个不透明指针,图形API返回的只是一个编号,你没办法通过这个编号访问显存,你只能让图形API这个黑盒子代劳,比如说把这个buffer绑定到shader的槽位上,而不是把地址送到Shader中;比如说调用什么BufferUpdate函数或者BufferCopy函数而不是我直接写入GPU的显存 > 这个事情在OpenGL里感知的特别明显,因为不论什么资源,都是一个unsigned int,第一个创建的贴图返回的就是1... 这是一个非常有意思也值得去思考的问题,我推荐一篇文章叫[no graphics api](https://www.sebastianaaltonen.com/blog/no-graphics-api)。 但在Vulkan中确实有这么个拓展允许shader直接通过显存地址访问到Buffer的,叫做[Buffer Device Address](https://docs.vulkan.org/guide/latest/buffer_device_address.html),这个拓展的支持相当广泛,很值得一用;但是glsl端的语法十分的糟糕,只能说捏着鼻子用的水平(事实上我觉得glsl的语法都糟糕的一批,推荐hlsl)。 其大概原理就是把buffer在Device端的地址作为一个64bit的数据送入shader中,shader可以直接根据这个地址去索引到Buffer,再也不需要和绑定/槽做搏斗了! ## 后端的贴图怎么来的? ImGUI接受一个ImTexID做为ImGui::image的参数,但问题是,这个ID是什么,怎么来的,为什么能被画出来? 这其实是个ImGui后端强相关的问题,比如说在OpenGL里这个TexID直接传入glGenTextures的uint32_t, 在Dx11后端是传入一个ID3D11ShaderResourceView,Vulkan后端比较诡异,是传入一个绑定上了贴图的DescriptorSet,这个SetLayout只有一个Texture。 在我的实现中,这个TextureID是我的RHI的一个view对象,我在绘制IMGUI的时候会把这个view加载到BindlessSet里,并且在绘制完成后卸载。 因为ImGui本身并不能拥有任何数据,所以每帧不停的Bind和UnBind我觉得也是迫不得已的事情。如果之前有其他渲染操作已经把这贴图上传到Bindless过,那么就少了一次UpdateDescriptorSet的开销,如果没有,那就是不得不承担之恶了 > 当然我也知道在某些实现中是贴图一创建就会上传到BindlessSet中,我没这么干.... 还有一部分的贴图是由ImGui创建的,比如说FontAtlas,这里就需要我们自己手动处理了,详情见后文的代码部分 ## 代码部分 这些代码大部分都调用的是我渲染器里的RHI,但其和API的Mapping不难找到,将就着看看吧 首先是定义一个用于绘制UI的RenderPass > DebugOverlayPass.h ```cpp #ifndef DEBUG_OVERLAY_PASS_H_ #define DEBUG_OVERLAY_PASS_H_ #include "Renderer/RenderPass.h" #include "Renderer/TextureResourceMgr.h" namespace Render { struct rs_rendertarget; class DebugOverlayPass : public RenderPass{ public: DebugOverlayPass(); ~DebugOverlayPass(); void init(); void deinit(); virtual void preDraw(rs_commandbuffer* cmdbuffer, Camera* cam)override; virtual void draw(rs_commandbuffer* cmdbuffer, Camera* cam)override; virtual void setGameView(TexturePtr image); void addDrawEntity(RenderEntity* entity); TexturePtr getOverlayTexture(); private: std::vector<RenderEntity*> mEntities; TexturePtr mRenderTexture = nullptr; rs_rendertarget* mRenderTarget = nullptr; TexturePtr mGameView = nullptr; int lastFrameWinX = 0; int lastFrameWinY = 0; }; } #endif //DEBUG_OVERLAY_PASS_H_ ``` > DebugOverlayPass.cpp ```cpp #include "Renderer/RenderPass/DebugOverlayPass.h" #include "Renderer/RenderSystem.h" #include "Renderer/EnginePass.h" #include "Renderer/UI/ImGuiManager.h" #include "Renderer/RenderDebuger.h" namespace Render { static PassDesc getPassDesc() { PassDesc desc; PassAttachment att{}; att.fmt = RenderTextureFormat::RGBA8; att.storeOp = StorageOp::Cached; att.loadOp = StorageOp::Cached; desc.attachments.push_back(att); desc.lastDepth = false; desc.writeDepth = false; return desc; } DebugOverlayPass::DebugOverlayPass() : RenderPass(PassName::GUIOverlay, getPassDesc()) { ClearColor clear{}; clear.rgba[0] = 0.; clear.rgba[1] = 0.; clear.rgba[2] = 0.; //We need alpha to be zero, which will be convience for the following compose pass. clear.rgba[3] = 0.; this->setClearData({ clear }, {}); } DebugOverlayPass::~DebugOverlayPass() { RenderSystem::instance()->destroyRenderTarget(mRenderTarget); mRenderTarget = nullptr; } void DebugOverlayPass::init() { IMGUIManager::instance()->init(); } void DebugOverlayPass::deinit() { IMGUIManager::instance()->deinit(); } void DebugOverlayPass::preDraw(rs_commandbuffer* cmdbuffer, Camera* cam) { if (!mRenderTexture) { mRenderTexture = TextureResourceManager::instance()->createEmpty(); } //1. get win size int winx, winy; RenderSystem::instance()->getWindowSize(winx, winy); if (winx * winy != 0 && (winx != lastFrameWinX || winy != lastFrameWinY)) { lastFrameWinX = winx; lastFrameWinY = winy; auto img = RenderSystem::instance()->createImage2D( 0, 0, ImageFormat::RGBA8_UNORM, lastFrameWinX, lastFrameWinY, 1, 1, 1, ImageUsage_Sampled | ImageUsage_ColorAttachment | ImageUsage_TransferSrc | ImageUsage_Storage ); if (mRenderTarget) { RenderSystem::instance()->destroyRenderTarget(mRenderTarget); } mRenderTarget = RenderSystem::instance()->createRendertarget( { img }, nullptr ); mRenderTexture->setRsImage(img, true); } } void DebugOverlayPass::draw(rs_commandbuffer* cmdbuffer, Camera* cam) { passBegin(); logicPassBegin(PassName::GUIOverlay); RenderMarker marker(cmdbuffer, "OverlayPass", 0.6, 0.3, 0.3, 1.); preDraw(cmdbuffer,cam); auto RenderSys = RenderSystem::instance(); RenderSys->setCurrentCamera(nullptr); //Blit gameview to cur ui render target if (mGameView) { RenderSys->cmdBlitCompute(cmdbuffer, mGameView, mGameView->getRsImage()->defaultView->viewKey, mRenderTexture, mRenderTexture->getRsImage()->defaultView->viewKey, Filter::Linear ); } IMGUIManager::instance()->draw(); for (auto& entity : mEntities) { RenderSys->updateParameters(cmdbuffer, entity, entity->getPass(this->getPassName())); } RenderSys->excutePendingBufferCopies(cmdbuffer); RenderSys->cmdBeginRenderPass(cmdbuffer, mRenderPass, mClrColor, mDsClear); RenderSys->cmdSetRendertarget(cmdbuffer, mRenderTarget); Rect2D nextRenderArea{}; updateViewportAndScissor(cmdbuffer, mRenderTarget); //Draw ui over game view for (auto entity : mEntities) { RenderSys->drawIndexed(cmdbuffer, entity, cam, entity->getPass(this->getPassName())); } RenderSys->cmdEndRenderPass(cmdbuffer); mEntities.clear(); logicPassEnd(PassName::GUIOverlay); passEnd(); } void DebugOverlayPass::setGameView(TexturePtr image) { mGameView = image; } void DebugOverlayPass::addDrawEntity(RenderEntity* entity) { mEntities.push_back(entity); } Render::TexturePtr DebugOverlayPass::getOverlayTexture() { return mRenderTexture; } } ``` 这部分负责绘制ImGui //ImGUiManager.h ```cpp #ifndef IMGUI_MANAGER_H_ #define IMGUI_MANAGER_H_ #include "function/InputDef.h" #include "common/Singleton.h" namespace Render { struct rs_image_view; struct rs_commandbuffer; class IMGUIManager :public Singleton<IMGUIManager>{ public: IMGUIManager(); //Mostly handle render related in this func void draw(); //Mostly handle IO in this func void update(); //0-100 void setImGuiScrollSensitivity(float x); //After render passes created. void init(); void deinit(); ~IMGUIManager(); void setTextureSamplerFilter(Filter filter); private: class ImGuiManagerPrivate* mDP; }; } #endif //!IMGUI_MANAGER_H_ ``` > ImguiManager.cpp ```cpp #include "render_resource.h" #include "Renderer/RenderSystem.h" #include "Renderer/UI/ImGuiManager.h" #include "Renderer/MaterialTemplateManager.h" #include "Renderer/MaterialManager.h" #include "Renderer/RenderEntity.h" #include "Renderer/EnginePass.h" #include "Renderer/RenderPass/DebugOverlayPass.h" #include "Renderer/SamplerResourceManager.h" #include "function/InputManager.h" #include "imgui.h" #include "Renderer/UI/ImGuiUtils.h" namespace Render { struct ImGuiKeyMapContainer { ImGuiKey data[(int)KeyCode::Max]{}; constexpr ImGuiKeyMapContainer() { data[(int)KeyCode::Space] = ImGuiKey_Space; data[(int)KeyCode::Apostrophe] = ImGuiKey_Apostrophe; data[(int)KeyCode::Comma] = ImGuiKey_Comma; data[(int)KeyCode::Minus] = ImGuiKey_Minus; data[(int)KeyCode::Period] = ImGuiKey_Period; data[(int)KeyCode::Slash] = ImGuiKey_Slash; data[(int)KeyCode::Semicolon] = ImGuiKey_Semicolon; data[(int)KeyCode::Equal] = ImGuiKey_Equal; data[(int)KeyCode::LeftBracket] = ImGuiKey_LeftBracket; data[(int)KeyCode::Backslash] = ImGuiKey_Backslash; data[(int)KeyCode::RightBracket] = ImGuiKey_RightBracket; data[(int)KeyCode::GraveAccent] = ImGuiKey_GraveAccent; data[(int)KeyCode::World1] = ImGuiKey_Oem102; for (int i = 0; i <= 9; ++i) { data[(int)KeyCode::Key0 + i] = static_cast<ImGuiKey>(ImGuiKey_0 + i); } for (int i = 0; i < 26; ++i) { data[(int)KeyCode::A + i] = static_cast<ImGuiKey>(ImGuiKey_A + i); } data[(int)KeyCode::Escape] = ImGuiKey_Escape; data[(int)KeyCode::Enter] = ImGuiKey_Enter; data[(int)KeyCode::Tab] = ImGuiKey_Tab; data[(int)KeyCode::Backspace] = ImGuiKey_Backspace; data[(int)KeyCode::Insert] = ImGuiKey_Insert; data[(int)KeyCode::Delete] = ImGuiKey_Delete; data[(int)KeyCode::Right] = ImGuiKey_RightArrow; data[(int)KeyCode::Left] = ImGuiKey_LeftArrow; data[(int)KeyCode::Down] = ImGuiKey_DownArrow; data[(int)KeyCode::Up] = ImGuiKey_UpArrow; data[(int)KeyCode::PageUp] = ImGuiKey_PageUp; data[(int)KeyCode::PageDown] = ImGuiKey_PageDown; data[(int)KeyCode::Home] = ImGuiKey_Home; data[(int)KeyCode::End] = ImGuiKey_End; data[(int)KeyCode::CapsLock] = ImGuiKey_CapsLock; data[(int)KeyCode::ScrollLock] = ImGuiKey_ScrollLock; data[(int)KeyCode::NumLock] = ImGuiKey_NumLock; data[(int)KeyCode::PrintScreen] = ImGuiKey_PrintScreen; data[(int)KeyCode::Pause] = ImGuiKey_Pause; for (int i = 0; i < 24; ++i) { data[(int)KeyCode::F1 + i] = static_cast<ImGuiKey>(ImGuiKey_F1 + i); } for (int i = 0; i <= 9; ++i) { data[(int)KeyCode::Kp0 + i] = static_cast<ImGuiKey>(ImGuiKey_Keypad0 + i); } data[(int)KeyCode::KpDecimal] = ImGuiKey_KeypadDecimal; data[(int)KeyCode::KpDivide] = ImGuiKey_KeypadDivide; data[(int)KeyCode::KpMultiply] = ImGuiKey_KeypadMultiply; data[(int)KeyCode::KpSubtract] = ImGuiKey_KeypadSubtract; data[(int)KeyCode::KpAdd] = ImGuiKey_KeypadAdd; data[(int)KeyCode::KpEnter] = ImGuiKey_KeypadEnter; data[(int)KeyCode::KpEqual] = ImGuiKey_KeypadEqual; data[(int)KeyCode::LeftShift] = ImGuiKey_LeftShift; data[(int)KeyCode::LeftControl] = ImGuiKey_LeftCtrl; data[(int)KeyCode::LeftAlt] = ImGuiKey_LeftAlt; data[(int)KeyCode::LeftSuper] = ImGuiKey_LeftSuper; data[(int)KeyCode::RightShift] = ImGuiKey_RightShift; data[(int)KeyCode::RightControl] = ImGuiKey_RightCtrl; data[(int)KeyCode::RightAlt] = ImGuiKey_RightAlt; data[(int)KeyCode::RightSuper] = ImGuiKey_RightSuper; data[(int)KeyCode::Menu] = ImGuiKey_Menu; } constexpr ImGuiKey operator[] (KeyCode key)const { return data[(int)key]; } }; static constexpr ImGuiKeyMapContainer KeyCodeToImGuiMapInstance; static const ImGuiKey MouseButtonToImGuiKeyMap[(int)Render::MouseButton::Max] = { ImGuiKey_MouseLeft, // Button1 / Left ImGuiKey_MouseRight, // Button2 / Right ImGuiKey_MouseMiddle, // Button3 / Middle ImGuiKey_MouseX1, // Button4 ImGuiKey_MouseX2, // Button5 ImGuiKey_None, // Button6 ImGuiKey_None, // Button7 ImGuiKey_None, // Button8 / Last }; static ImGuiMouseButton_ toImMouseBtn(MouseButton btn) { switch (btn) { case Render::MouseButton::Button4: case Render::MouseButton::Button5: case Render::MouseButton::Button6: case Render::MouseButton::Button7: case Render::MouseButton::Button8: case Render::MouseButton::Max: return ImGuiMouseButton_COUNT; break; case Render::MouseButton::Left: { return ImGuiMouseButton_::ImGuiMouseButton_Left; break; } case Render::MouseButton::Right: { return ImGuiMouseButton_::ImGuiMouseButton_Right; break; } case Render::MouseButton::Middle: { return ImGuiMouseButton_::ImGuiMouseButton_Middle; break; } default: break; } return ImGuiMouseButton_COUNT; } class ImGuiRender : public RenderEntity { public: MaterialPtr mMat = nullptr; public: ImGuiRender(MaterialPtr mat) { this->setRenderMask(RenderMask::Gui2D); mMat = mat; } Material* getMaterial() override { return mMat.get(); } AxisAlignedBoundingBox getWorldBounding()override { return AxisAlignedBoundingBox(); } void updateEntityCommonData() override { return; } void updateEntityCommonDataImpl(Pass*) override { return; } }; class ImGuiManagerPrivate { public: bool isInited = false; uint32_t vtxSize = 0; // By default rs_buffer* vertexBuffer = nullptr; uint32_t idxSize = 0; // By default rs_buffer* idxBuffer = nullptr; uint32_t triToDraw = 0; // By default uint32_t drawCallNum = 0; // By default rs_buffer* triangleAttributeIdxBuffer = nullptr; rs_buffer* attributeBuffer = nullptr;//Perdraw call struct ViewportInfo{ vec2 scale; vec2 offset; }mViewportInfo; struct PerDrawcallAttribute { ViewportInfo info; uint32_t texIdx; vec2 scissorMin; vec2 scissorMax; uint32_t isTexDepth = 0; }; MaterialPtr material = nullptr; ImGuiRender* imguiRender = nullptr; uint32_t imCreateTextureIDCnt = 1; SamplerPtr mImSampler = nullptr; //CONFIGS float scrollSens = 10.; //ID = 0 ---> invalid! std::map<uint32_t, std::pair<uint32_t,TexturePtr>> mImCreatedTextures{}; decltype(std::chrono::steady_clock::now()) mLastFrameTime; void init(); //TODO: set basic info when frame started. void onReSizeBuffer(uint32_t vtx,uint32_t idx,uint32_t texSize,uint32_t drawcallNums); void updateImTexture(ImTextureData* tex); uint32_t createImTexture(ImTextureData* tex); void destroyImTexture(ImTextureData* tex); void deinit(); }; void ImGuiManagerPrivate::init() { //----------------------------------------------------------// //--------------IMGUI SETUP INFO----------------------------// ImGui::CreateContext(); ImGuiIO& io = ImGui::GetIO(); (void)io; //Only work in bindless mode. if (!RenderSystem::instance()->isBindlessEnabled()) return; isInited = true; io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls //io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking //TODO: backend support multi viewport? //io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable Multi-Viewport / Platform Windows //io.ConfigDpiScaleFonts = true; // [Experimental] Automatically overwrite style.FontScaleDpi in Begin() when Monitor DPI changes. This will scale fonts but _NOT_ scale sizes/padding for now. //Backend setup io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // Setup Dear ImGui style ImGui::StyleColorsDark(); //----------------------------------------------------------// //-------------IMGUI RENDER INFO----------------------------// ShaderStageInfo shaderStage = { {ShaderStage::Vertex ,"../shader/ImGuiBindless.vs"}, {ShaderStage::Fragment ,"../shader/ImGuiBindless.ps"}, }; RenderState renderState{}; BlendState blendInfo{}; blendInfo.blendEnable = true; blendInfo.colorBlendOp = BlendOp::Add; blendInfo.srcAlphaBlend = BlendFactor::One; blendInfo.dstAlphaBlend = BlendFactor::OneMinusSrcAlpha; blendInfo.srcColorBlend = BlendFactor::SrcAlpha; blendInfo.dstColorBlend = BlendFactor::OneMinusSrcAlpha; renderState.blendStates.push_back(blendInfo); renderState.depthWriteEnable = false; renderState.depthTestEnable = false; VertexInputDescription vtxIA{}; auto materialTemplate = MaterialTemplateManager::instance()->createMaterialTemplate( Name("ImGuiMatTemp"), shaderStage, renderState, vtxIA ); materialTemplate->createMaterialPass(PassName::GUIOverlay); material = MaterialManager::instance()->createMaterial<Material>(Name("ImGui"),materialTemplate); if (!mImSampler) { mImSampler = SamplerResourceManager::instance()->getOrCreateSampler({}); } material->bindParameter("u_sampler", mImSampler); imguiRender = new ImGuiRender(material); //----------------------------------------------------------// int winx, winy; RenderSystem::instance()->getWindowSize(winx, winy); io.DisplaySize.x = winx; io.DisplaySize.y = winy; ImGui::NewFrame(); } void ImGuiManagerPrivate::onReSizeBuffer(uint32_t vtx, uint32_t idx, uint32_t triCount, uint32_t drawcallNum) { BufferDesc desc{}; desc.bufUsage = BufferType_Storage; desc.mappable = true; if (vtxSize < vtx && vtx > 0) { const uint32_t ImDrawVertSize = sizeof(ImDrawVert); vtxSize = vtx; RenderSystem::instance()->destroyBuffer(vertexBuffer); vertexBuffer = nullptr; desc.byteSize = ImDrawVertSize * vtxSize; vertexBuffer = RenderSystem::instance()->createBuffer(nullptr, 0, desc); material->bindParameter( "u_vertexList", vertexBuffer ); } if (idxSize < idx && idx > 0) { idxSize = idx; RenderSystem::instance()->destroyBuffer(idxBuffer); idxBuffer = nullptr; desc.byteSize = sizeof(ImDrawIdx) * idxSize; idxBuffer = RenderSystem::instance()->createBuffer(nullptr, 0, desc); material->bindParameter( "u_indexList", idxBuffer ); } //triangleAttributeIdxBuffer size is always equals to idxSize / 3 //map each triangle to drawcall's attribute uint32_t triangleToDrawSize = triCount; if(triToDraw < triangleToDrawSize && triangleToDrawSize > 0) { triToDraw = triangleToDrawSize; RenderSystem::instance()->destroyBuffer(triangleAttributeIdxBuffer); triangleAttributeIdxBuffer = nullptr; desc.byteSize = sizeof(u32) * triToDraw; triangleAttributeIdxBuffer = RenderSystem::instance()->createBuffer(nullptr, 0, desc); material->bindParameter( "u_triAttIdxList", triangleAttributeIdxBuffer ); } if (drawcallNum > this->drawCallNum) { this->drawCallNum = drawcallNum; BufferDesc descBuffer{}; descBuffer.bufUsage = BufferType_Storage; descBuffer.mappable = true; descBuffer.byteSize = sizeof(PerDrawcallAttribute) * drawCallNum; attributeBuffer = RenderSystem::instance()->createBuffer(nullptr, 0, descBuffer); material->bindParameter("u_attrList", attributeBuffer); } //if (textureSize < tex) { // RenderSystem::instance()->destroyBuffer(indexAttributeIdxBuffer); // desc.byteSize = sizeof(uint32_t) * textureSize; // indexAttributeIdxBuffer = RenderSystem::instance()->createBuffer(nullptr, 0, desc); // material->bindParameter( // "u_textureList", indexAttributeIdxBuffer // ); //} } void ImGuiManagerPrivate::updateImTexture(ImTextureData* tex) { int begx = tex->UpdateRect.x; int begy = tex->UpdateRect.y; int width = tex->UpdateRect.w; int height = tex->UpdateRect.h; std::vector<uint32_t> updateData; updateData.reserve(width * height); for (int y = begy;y < begy + height;++y) { for (int x = begx;x < begx + width;++x) { auto pix = *(uint32_t*)tex->GetPixelsAt(x, y); updateData.push_back(pix); } } auto backendID = (uint32_t)tex->BackendUserData; auto itor = mImCreatedTextures.find(backendID); if (itor == mImCreatedTextures.end()) { assert(false); return; } auto& texture_view_pair = itor->second; auto& texture = texture_view_pair.second; auto img = texture->getRsImage(); RenderSystem::instance()->updateImageData( img, updateData.data(), updateData.size() * sizeof(uint32_t), begx, begy, 0, width, height, 1, 0, 1, 1); tex->SetStatus(ImTextureStatus_OK); } uint32_t ImGuiManagerPrivate::createImTexture(ImTextureData* tex) { IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == nullptr); IM_ASSERT(tex->Format == ImTextureFormat_RGBA32); auto backEndID = imCreateTextureIDCnt++; *((uint64_t*)& tex->BackendUserData) = (uint64_t)backEndID; auto texBackend = TextureResourceManager::instance()->createEmpty(); std::vector<uint32_t> mPixelsInTile; mPixelsInTile.reserve(tex->Width * tex->Height); for (int y = 0;y < tex->Height;++y) { for (int x = 0;x < tex->Width;++x){ mPixelsInTile.push_back( *(uint32_t*)tex->GetPixelsAt(x, y)); } } auto image = RenderSystem::instance()->createImage2D( mPixelsInTile.data(), sizeof(uint32_t) * mPixelsInTile.size(), ImageFormat::RGBA8_UNORM, tex->Width, tex->Height, 1, 1, 1); texBackend->setRsImage(image); tex->SetTexID(toImTex(image->defaultView)); tex->SetStatus(ImTextureStatus_OK); //For im created texture we need to extend its lifetime in bindless auto globalBindless = RenderSystem::instance()->getGlobalBindlessData(); auto idx = RenderSystem::instance()->updateGlobalBindlessDataTexture(globalBindless, texBackend->getRsImage()->defaultView); mImCreatedTextures.insert({backEndID,{idx,texBackend}}); return backEndID; } void ImGuiManagerPrivate::destroyImTexture(ImTextureData* tex) { auto backendID = (uint32_t)tex->BackendUserData; auto itor = mImCreatedTextures.find(backendID); if (itor == mImCreatedTextures.end()) { assert(false); return; } auto bindlessIdx = itor->second.first; auto globalBindless = RenderSystem::instance()->getGlobalBindlessData(); RenderSystem::instance()->unbindGlobalBindlessDataTexture(globalBindless, bindlessIdx); mImCreatedTextures.erase(backendID); tex->SetStatus(ImTextureStatus_Destroyed); tex->SetTexID(ImTextureID_Invalid); } void ImGuiManagerPrivate::deinit() { isInited = false; delete imguiRender; RenderSystem::instance()->destroyBuffer(vertexBuffer); vertexBuffer = nullptr; RenderSystem::instance()->destroyBuffer(triangleAttributeIdxBuffer); triangleAttributeIdxBuffer = nullptr; RenderSystem::instance()->destroyBuffer(idxBuffer); idxBuffer = nullptr; RenderSystem::instance()->destroyBuffer(attributeBuffer); attributeBuffer = nullptr; ImGui::DestroyContext(); mImCreatedTextures.clear(); } IMGUIManager::IMGUIManager() { mDP = new ImGuiManagerPrivate; mDP->mLastFrameTime = std::chrono::steady_clock::now(); } void IMGUIManager::draw() { auto RenderSys = RenderSystem::instance(); auto io = ImGui::GetIO(); //This act as a imgui engine backend, use engine api to render widgets ImGui::EndFrame(); ImGui::Render(); if (!mDP->isInited)return; auto imDrawData = ImGui::GetDrawData(); auto globalBindless = RenderSystem::instance()->getGlobalBindlessData(); //Update textures std::vector<std::pair<rs_image_view*, uint32_t>> viewBindlessIndexPairs{}; for (ImTextureData* tex : *imDrawData->Textures) { if (tex->Status == ImTextureStatus_WantCreate) { mDP->createImTexture(tex); } if (tex->Status == ImTextureStatus_WantDestroy && tex->UnusedFrames >= 5) { mDP->destroyImTexture(tex); } //update just a little bit if (tex->Status == ImTextureStatus_WantUpdates) { mDP->updateImTexture(tex); } rs_image_view* view = (rs_image_view*)tex->GetTexID(); if (tex->Status == ImTextureStatus_OK) { if (view == nullptr) { assert(view != nullptr); continue; } if (view->viewKey.getUAVAccess() != UAVAccess::ReadOnly) { assert(view->viewKey.getUAVAccess() == UAVAccess::ReadOnly && "Only SRV CAN BE RENDER IN IMGUI!!!"); continue; } auto texIdx = RenderSystem::instance()->updateGlobalBindlessDataTexture( globalBindless, view ); RenderSys->markGlobalBindlessDataTexture(globalBindless, view); viewBindlessIndexPairs.push_back({ view,texIdx }); } } //parse drawData, generate vertex buffer, index buffer, update attribute,update textures to global bindless data //And dispatch dc. auto vtxSize = imDrawData->TotalVtxCount; auto idxSize = imDrawData->TotalIdxCount; auto imDrawListCnt = imDrawData->CmdListsCount; int totalIndexWillDraw = 0; int totalDc = 0; for (auto drawList : imDrawData->CmdLists) { for (auto& cmd : drawList->CmdBuffer) { totalDc++; totalIndexWillDraw += cmd.ElemCount; } } int triangleCount = totalIndexWillDraw / 3.; int curVtxPos = 0; int curIdxPos = 0; int curTriPos = 0; this->mDP->onReSizeBuffer(vtxSize, idxSize, triangleCount, totalDc); //This is actually a triangle -> drawcall's attribute mapping //And per drawcall has it's own attribute //Like: scissor, textureid, viewport info std::vector<u32> triAttIdToFill(triangleCount); std::vector<ImGuiManagerPrivate::PerDrawcallAttribute> perDrawCallAttributes{}; perDrawCallAttributes.reserve(totalDc); ImGuiManagerPrivate::PerDrawcallAttribute attr{}; ImGuiManagerPrivate::ViewportInfo vpInfo{}; for (int i = 0;i < imDrawListCnt; ++i) { //Fill viewport info, each drawlist shares one viewport ImVec2 clipOff = imDrawData->DisplayPos; // (0,0) unless using multi-viewports / but we do not support that.... ImVec2 clipScale = imDrawData->FramebufferScale; // (1,1) unless using retina display which are often (2,2) vpInfo.scale = vec2(2.0f / imDrawData->DisplaySize.x, 2.0f / imDrawData->DisplaySize.y); vpInfo.offset = vec2(-1.0f - imDrawData->DisplayPos.x * vpInfo.scale[0], -1.0f - imDrawData->DisplayPos.y * vpInfo.scale[1]); attr.info = vpInfo; //UPDATE IDX,VTX BUFFER //They are shared inside drawlist //assert(imDrawListCnt == 1); auto vtxBuffer = mDP->vertexBuffer; auto idxBuffer = mDP->idxBuffer; auto attributeIndexBuffer = mDP->triangleAttributeIdxBuffer; const ImDrawList* imDrawList = imDrawData->CmdLists[i]; //1. copy vtx data const auto& vtxToCpy = imDrawList->VtxBuffer; auto vtxByteSize = sizeof(ImDrawVert) * vtxToCpy.size(); auto vtxOffset = sizeof(ImDrawVert) * curVtxPos; RenderSys->updateBufferData( vtxBuffer, vtxToCpy.Data, vtxByteSize, vtxOffset ); //2. copy idx data const auto& idxToCpy = imDrawList->IdxBuffer; auto idxByteSize = sizeof(ImDrawIdx) * idxToCpy.size(); auto idxOffset = sizeof(ImDrawIdx) * curIdxPos; if (curIdxPos > 0) { std::vector<ImDrawIdx>finalIndex(idxToCpy.size()); for (int iid = 0;iid < idxToCpy.size();++iid) { finalIndex[iid] = idxToCpy.Data[iid] + curVtxPos; } RenderSys->updateBufferData( idxBuffer, finalIndex.data(), idxByteSize, idxOffset ); }else { RenderSys->updateBufferData( idxBuffer, idxToCpy.Data, idxByteSize, idxOffset ); } //3. construct attribute data, and setup triangle to drawcall's attribute mapping //Look into each im draw command //In imgui //Each draw cmd is a drawcall //and each drawcall shares one texture,one scissor //draw call only be generated when texture/scissor changed. const auto& imCmds = imDrawList->CmdBuffer; for (int cmdIdx = 0;cmdIdx < imCmds.size(); ++cmdIdx) { const auto& imCmd = imCmds[cmdIdx]; auto idxBeg = imCmd.IdxOffset; auto idxSize = imCmd.ElemCount; auto triangleBegin = curTriPos; auto triangleCount = u32(idxSize / 3); rs_image_view* view = (rs_image_view*)imCmd.GetTexID(); auto texIdx = RenderSystem::instance()->updateGlobalBindlessDataTexture( globalBindless, view ); RenderSys->markGlobalBindlessDataTexture(globalBindless, view); viewBindlessIndexPairs.push_back({ view,texIdx }); uint32_t drawcallIdToFill = perDrawCallAttributes.size(); uint32_t texidToFill = view->bindlessIndex; uint32_t isTexDepth = view->viewKey.getAspect() != ViewAspect::Color ? 1 : 0; assert(view != nullptr && view->viewKey.getUAVAccess() == UAVAccess::ReadOnly && view->bindlessIndex != INVALID_BINDLESS_INDEX); if (view == nullptr || view->viewKey.getUAVAccess() != UAVAccess::ReadOnly || view->bindlessIndex == INVALID_BINDLESS_INDEX) { assert(false && "This imgui render may be wrong because texture binding error"); texidToFill = 0; } curTriPos += triangleCount; //Fill attribute index. std::fill(triAttIdToFill.begin() + triangleBegin, triAttIdToFill.begin() + triangleBegin + triangleCount, drawcallIdToFill ); //Fill attribute... attr.texIdx = texidToFill; attr.isTexDepth = isTexDepth; auto imDisplaySize = ImGui::GetIO().DisplaySize; vec2 displaySize(imDisplaySize.x, imDisplaySize.y); vec2 clipMin = (vec2(imCmd.ClipRect.x - clipOff.x, imCmd.ClipRect.y - clipOff.y) * vec2(clipScale.x,clipScale.y) / displaySize ) * 2 - vec2(1); vec2 clipMax = (vec2(imCmd.ClipRect.z - clipOff.x, imCmd.ClipRect.w - clipOff.y) * vec2(clipScale.x, clipScale.y) / displaySize ) * 2 - vec2(1); attr.scissorMin = clipMin; attr.scissorMax = clipMax; perDrawCallAttributes.push_back(attr); } curVtxPos += vtxToCpy.size(); curIdxPos += idxToCpy.size(); } if (mDP->triangleAttributeIdxBuffer) { RenderSys->updateBufferData(mDP->triangleAttributeIdxBuffer, triAttIdToFill.data(), triAttIdToFill.size() * sizeof(uint32_t), 0); } if (mDP->attributeBuffer) { //Update per drawcall attribute data.... RenderSys->updateBufferData(mDP->attributeBuffer, perDrawCallAttributes.data(), sizeof(attr) * perDrawCallAttributes.size(), 0); } //Update Render entity data. auto entity = mDP->imguiRender; auto& renderInfo = mDP->imguiRender->getRenderInfo(); renderInfo.indexType = IndexType::Uint32; renderInfo.idxCount = idxSize; renderInfo.indexBuffer = nullptr; //We can't keep these data for long..... for (auto& [view, idx] : viewBindlessIndexPairs) { RenderSys->unbindGlobalBindlessDataTexture(globalBindless, idx); } ImGui::NewFrame(); ///////////////////////////////////////////////////////// if(idxSize > 0) { auto renderPass = (DebugOverlayPass*)RenderSys->getRenderPass(PassName::GUIOverlay); renderPass->addDrawEntity(entity); } } void IMGUIManager::update() { ImGuiIO& io = ImGui::GetIO(); (void)io; //Display size int winx, winy; RenderSystem::instance()->getWindowSize(winx, winy); io.DisplaySize.x = winx; io.DisplaySize.y = winy; //Add input info auto inputMgr = InputManager::instance(); bool needConsume = io.WantCaptureKeyboard; //Key? for (int i = 0;i < int(KeyCode::Max);++i) { KeyCode code = KeyCode(i); ImGuiKey imKey = toImKey(code); if (imKey == ImGuiKey::ImGuiKey_None)continue; bool keyPressed = inputMgr->isKeyPressed(code); bool keyReleased = inputMgr->isKeyReleased(code); if (keyPressed) { io.AddKeyEvent(imKey, true); } if (keyReleased) { io.AddKeyEvent(imKey, false); } if (needConsume) { inputMgr->consumeKey(code); } } //Input text? needConsume = io.WantTextInput; while (inputMgr->peekChar() > 0) { io.AddInputCharacter(inputMgr->consumeChar()); } //Mouse? needConsume = io.WantCaptureMouse; for (int i = 0;i < (int)MouseButton::Max;++i) { MouseButton btn = (MouseButton)i; ImGuiKey imBtn = toImKey(btn); ImGuiMouseButton_ imMsBtn = toImMouseBtn(btn); bool mousePressed = inputMgr->isMousePressed(btn); bool mouseRelease = inputMgr->isMouseReleased(btn); if (mousePressed) { //Mouse is somehow a kind of key in im //But it also has its own func.... if (imMsBtn != ImGuiMouseButton_::ImGuiMouseButton_COUNT) { io.AddMouseButtonEvent(imMsBtn, true); } else { io.AddKeyEvent(imBtn, true); } } if (mouseRelease) { if (imMsBtn != ImGuiMouseButton_::ImGuiMouseButton_COUNT) { io.AddMouseButtonEvent(imMsBtn, false); } else { io.AddKeyEvent(imBtn, false); } } double mouseX,mouseY; double mouseScX, mouseScY; inputMgr->getCursorPos(mouseX,mouseY); inputMgr->getMouseScroll(mouseScX, mouseScY); vec2 scroll(mouseScX, mouseScY); scroll = scroll * 1. / 100 * mDP->scrollSens; io.AddMousePosEvent(mouseX, mouseY); io.AddMouseWheelEvent(scroll.x, scroll.y); if (needConsume) { inputMgr->consumeMouse(btn); inputMgr->consumeMouseMove(); } } //Extra things auto curClock = std::chrono::steady_clock::now(); auto dur = curClock - mDP->mLastFrameTime; float dt = std::chrono::duration_cast<std::chrono::microseconds>(dur).count() / 1000. / 1000.; if (dt <= 0. || dt > 100.) { volatile int break1 = 0; } mDP->mLastFrameTime = curClock; io.DeltaTime = dt; } void IMGUIManager::setImGuiScrollSensitivity(float x) { mDP->scrollSens = x; } void IMGUIManager::init() { mDP->init(); } void IMGUIManager::deinit() { mDP->deinit(); } IMGUIManager::~IMGUIManager() { delete mDP; mDP = NULL; } void IMGUIManager::setTextureSamplerFilter(Filter filter) { SamplerDesc desc{}; desc.minFilter = filter; desc.magFilter = filter; mDP->mImSampler = SamplerResourceManager::instance()->getOrCreateSampler(desc); if (mDP->material) { mDP->material->bindParameter("u_sampler", mDP->mImSampler); } } ImTextureID toImTex(rs_image_view* view) { return ImTextureID(view); } ImGuiKey toImKey(KeyCode code) { return KeyCodeToImGuiMapInstance[code]; } ImGuiKey toImKey(MouseButton btn) { return MouseButtonToImGuiKeyMap[(int)btn]; } } ``` 这部分是Shader,可能有些...难懂? > imgui.fwd > 这个文件中定义了vs和ps中共享的所有数据结构 > 以及shader中绑定的内容 ```glsl #include "ShaderResource.inl" #if !defined(BINDLESS_ENABLE) ERROR: ONLY WORKING UNDER BINDLESS MODE #endif struct PerVtxData{ float i_posx; float i_posy; float i_uvx; float i_uvy; uint i_color; }; struct Attribute{ float displayScaleX; float displayScaleY; float displayOffsetX; float displayOffsetY; uint texIdx; float scissorXMin; float scissorYMin; float scissorXMax; float scissorYMax; uint isDepth; }; DECL_RBUFFER_STD430_BEG(VertexList) PerVtxData v[]; DECL_RBUFFER_STD430_END DECL_RBUFFER_STD430_BEG(IndexList) int i[]; DECL_RBUFFER_STD430_END DECL_RBUFFER_STD430_BEG(AttributeList) Attribute attr[]; DECL_RBUFFER_STD430_END DECL_RBUFFER_STD430_BEG(TriangleAttributeIndexList) int t[]; DECL_RBUFFER_STD430_END DECL_RBUFFER_STD430_BEG(ViewportInfo) vec2 displayScale; vec2 displayOffset; DECL_RBUFFER_STD430_END RESOURCE_DECL_BEG(4) SLOT_BUFFER_STD430(4, VertexList, u_vertexList) SLOT_BUFFER_STD430(4, IndexList, u_indexList) SLOT_BUFFER_STD430(4, TriangleAttributeIndexList, u_triAttIdxList) SLOT_BUFFER_STD430(4, AttributeList, u_attrList) SLOT_SAMPLER (4, Sampler, u_sampler) RESOURCE_DECL_END ``` > ImGuiBindless.vs ```glsl #version 450 #include "ShaderResource.inl" #include "BindlessSet.inl" #include "imgui.fwd" layout(location = 0) out vec2 o_pos; layout(location = 1) out vec2 o_uv; layout(location = 2) out vec4 o_color; layout(location = 3) flat out ivec2 o_texInfo; layout(location = 4) flat out vec4 o_scissor; void main(){ int curIndexIdx = gl_VertexIndex; int curVtxIdx = GetBuffer(u_indexList).i[curIndexIdx]; PerVtxData curVtxData = GetBuffer(u_vertexList).v[curVtxIdx]; o_uv = vec2(curVtxData.i_uvx, curVtxData.i_uvy); uint colorU32 = curVtxData.i_color; float colorr = colorU32 & 0xFF; colorU32 = colorU32 >> 8; float colorg = colorU32 & 0xFF; colorU32 = colorU32 >> 8; float colorb = colorU32 & 0xFF; colorU32 = colorU32 >> 8; float colora = colorU32 & 0xFF; vec4 color = vec4(colorr, colorg, colorb, colora) * (1.f / 255.f); o_color = color; //Each tri shares the same texture //Each tri made of 3 vertices uint curTriangleIdx = uint(curIndexIdx / 3); int attrIdx = GetBuffer(u_triAttIdxList).t[curTriangleIdx]; Attribute att = GetBuffer(u_attrList).attr[attrIdx]; vec2 displayScale = vec2(att.displayScaleX, att.displayScaleY); vec2 displayOffset = vec2(att.displayOffsetX, att.displayOffsetY); o_texInfo = ivec2(att.texIdx, att.isDepth); vec2 pos = vec2(curVtxData.i_posx, curVtxData.i_posy); pos = pos * displayScale + displayOffset; o_pos = pos; o_scissor = vec4(att.scissorXMin, att.scissorYMin, att.scissorXMax, att.scissorYMax); gl_Position = vec4(pos, 0.0, 1.0); } ``` > ImGuiBindless.ps ```glsl #version 450 #include "ShaderResource.inl" #include "BindlessSet.inl" #include "imgui.fwd" layout(location = 0) in vec2 i_pos; layout(location = 1) in vec2 i_uv; layout(location = 2) in vec4 i_color; layout(location = 3) flat in ivec2 i_texInfo; layout(location = 4) flat in vec4 i_scissor; layout(location = 0) out vec4 o_col; void main(){ if(i_pos.x < i_scissor.x || i_pos.x > i_scissor.z || i_pos.y < i_scissor.y || i_pos.y > i_scissor.w ){ discard; } vec4 texCol = texture( GetSampledTextureByIdx(i_texInfo.x,u_sampler), i_uv ); if(i_texInfo.y > 0){ //Is depth? texCol = vec4(texCol.x,texCol.x,texCol.x,1); } o_col = i_color * texCol; } ``` ## 截帧 