ImguiSystem: improve API and performance

Instead of manually calling each surface instance directly from CImguiSystem::SampleFrame(), allow the caller to install their own surfaces. Also, only lock the buffer mutex if we are going to take a snapshot of the draw data. The system now also only renders to the backend if there's draw data to be rendered. Slightly reworked and reordered the control flow of the system to make it easier to catch bugs. The system can now also be disabled using the -noimgui command line option.
This commit is contained in:
Kawe Mazidjatari 2024-11-15 19:48:03 +01:00
parent 68b7c25a10
commit 8de652b4c9
4 changed files with 105 additions and 92 deletions

View File

@ -8,9 +8,6 @@
#include "engine/sys_mainwind.h" #include "engine/sys_mainwind.h"
#include "windows/id3dx.h" #include "windows/id3dx.h"
#include "IBrowser.h"
#include "IConsole.h"
#include "imgui_system.h" #include "imgui_system.h"
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
@ -18,7 +15,8 @@
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
CImguiSystem::CImguiSystem() CImguiSystem::CImguiSystem()
{ {
m_systemInitState = ImguiSystemInitStage_e::IM_PENDING_INIT; m_initialized = false;
m_hasNewFrame = false;
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
@ -28,7 +26,7 @@ CImguiSystem::CImguiSystem()
bool CImguiSystem::Init() bool CImguiSystem::Init()
{ {
Assert(ThreadInMainThread(), "CImguiSystem::Init() should only be called from the main thread!"); Assert(ThreadInMainThread(), "CImguiSystem::Init() should only be called from the main thread!");
Assert(m_systemInitState == ImguiSystemInitStage_e::IM_PENDING_INIT, "CImguiSystem::Init() called recursively?"); Assert(!IsInitialized(), "CImguiSystem::Init() called recursively?");
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
IMGUI_CHECKVERSION(); IMGUI_CHECKVERSION();
@ -55,12 +53,12 @@ bool CImguiSystem::Init()
!ImGui_ImplDX11_Init(D3D11Device(), D3D11DeviceContext())) !ImGui_ImplDX11_Init(D3D11Device(), D3D11DeviceContext()))
{ {
Assert(0); Assert(0);
m_systemInitState = ImguiSystemInitStage_e::IM_INIT_FAILURE;
return false; return false;
} }
m_systemInitState = ImguiSystemInitStage_e::IM_SYSTEM_INIT; m_initialized = true;
m_hasNewFrame = false;
return true; return true;
} }
@ -70,23 +68,62 @@ bool CImguiSystem::Init()
void CImguiSystem::Shutdown() void CImguiSystem::Shutdown()
{ {
Assert(ThreadInMainThread(), "CImguiSystem::Shutdown() should only be called from the main thread!"); Assert(ThreadInMainThread(), "CImguiSystem::Shutdown() should only be called from the main thread!");
Assert(m_systemInitState != ImguiSystemInitStage_e::IM_PENDING_INIT, "CImguiSystem::Shutdown() called recursively?"); Assert(IsInitialized(), "CImguiSystem::Shutdown() called recursively?");
// Nothing to shutdown.
if (m_systemInitState == ImguiSystemInitStage_e::IM_PENDING_INIT)
return;
AUTO_LOCK(m_snapshotBufferMutex); AUTO_LOCK(m_snapshotBufferMutex);
AUTO_LOCK(m_inputEventQueueMutex); AUTO_LOCK(m_inputEventQueueMutex);
m_systemInitState = ImguiSystemInitStage_e::IM_PENDING_INIT;
ImGui_ImplDX11_Shutdown(); ImGui_ImplDX11_Shutdown();
ImGui_ImplWin32_Shutdown(); ImGui_ImplWin32_Shutdown();
ImGui::DestroyContext(); ImGui::DestroyContext();
m_snapshotData.Clear(); m_snapshotData.Clear();
m_initialized = false;
m_hasNewFrame = false;
}
//-----------------------------------------------------------------------------
// Add an imgui surface.
//-----------------------------------------------------------------------------
void CImguiSystem::AddSurface(CImguiSurface* const surface)
{
Assert(IsInitialized());
m_surfaceList.AddToTail(surface);
}
//-----------------------------------------------------------------------------
// Remove an imgui surface.
//-----------------------------------------------------------------------------
void CImguiSystem::RemoveSurface(CImguiSurface* const surface)
{
Assert(!IsInitialized());
m_surfaceList.FindAndRemove(surface);
}
//-----------------------------------------------------------------------------
// Draws the ImGui panels and applies all queued input events.
//-----------------------------------------------------------------------------
void CImguiSystem::SampleFrame()
{
Assert(ThreadInMainThread(), "CImguiSystem::SampleFrame() should only be called from the main thread!");
Assert(IsInitialized());
AUTO_LOCK(m_inputEventQueueMutex);
ImGui_ImplDX11_NewFrame();
ImGui_ImplWin32_NewFrame();
ImGui::NewFrame();
FOR_EACH_VEC(m_surfaceList, i)
{
CImguiSurface* const surface = m_surfaceList[i];
surface->RunFrame();
}
ImGui::EndFrame();
ImGui::Render();
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
@ -97,48 +134,19 @@ void CImguiSystem::Shutdown()
void CImguiSystem::SwapBuffers() void CImguiSystem::SwapBuffers()
{ {
Assert(ThreadInMainThread(), "CImguiSystem::SwapBuffers() should only be called from the main thread!"); Assert(ThreadInMainThread(), "CImguiSystem::SwapBuffers() should only be called from the main thread!");
Assert(IsInitialized());
if (m_systemInitState < ImguiSystemInitStage_e::IM_FRAME_SAMPLED) ImDrawData* const drawData = ImGui::GetDrawData();
Assert(drawData);
// Nothing has been drawn, nothing to swap.
if (!drawData->CmdListsCount)
return; return;
AUTO_LOCK(m_snapshotBufferMutex); AUTO_LOCK(m_snapshotBufferMutex);
ImDrawData* const drawData = ImGui::GetDrawData();
// Nothing has been drawn, nothing to swap
if (!drawData)
return;
m_snapshotData.SnapUsingSwap(drawData, ImGui::GetTime()); m_snapshotData.SnapUsingSwap(drawData, ImGui::GetTime());
m_hasNewFrame = true;
if (m_systemInitState == ImguiSystemInitStage_e::IM_FRAME_SAMPLED)
m_systemInitState = ImguiSystemInitStage_e::IM_FRAME_SWAPPED;
}
//-----------------------------------------------------------------------------
// Draws the ImGui panels and applies all queued input events.
//-----------------------------------------------------------------------------
void CImguiSystem::SampleFrame()
{
Assert(ThreadInMainThread(), "CImguiSystem::SampleFrame() should only be called from the main thread!");
if (m_systemInitState == ImguiSystemInitStage_e::IM_PENDING_INIT)
return;
AUTO_LOCK(m_inputEventQueueMutex);
ImGui_ImplDX11_NewFrame();
ImGui_ImplWin32_NewFrame();
ImGui::NewFrame();
g_Browser.RunFrame();
g_Console.RunFrame();
ImGui::EndFrame();
ImGui::Render();
if (m_systemInitState == ImguiSystemInitStage_e::IM_SYSTEM_INIT)
m_systemInitState = ImguiSystemInitStage_e::IM_FRAME_SAMPLED;
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
@ -146,16 +154,14 @@ void CImguiSystem::SampleFrame()
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
void CImguiSystem::RenderFrame() void CImguiSystem::RenderFrame()
{ {
if (m_systemInitState < ImguiSystemInitStage_e::IM_FRAME_SWAPPED) Assert(IsInitialized());
AUTO_LOCK(m_snapshotBufferMutex);
if (!m_hasNewFrame)
return; return;
{
AUTO_LOCK(m_snapshotBufferMutex);
ImGui_ImplDX11_RenderDrawData(&m_snapshotData.DrawData); ImGui_ImplDX11_RenderDrawData(&m_snapshotData.DrawData);
} m_hasNewFrame = false;
if (m_systemInitState == ImguiSystemInitStage_e::IM_FRAME_SAMPLED)
m_systemInitState = ImguiSystemInitStage_e::IM_FRAME_RENDERED;
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
@ -163,7 +169,7 @@ void CImguiSystem::RenderFrame()
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
LRESULT CImguiSystem::MessageHandler(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) LRESULT CImguiSystem::MessageHandler(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{ {
if (ImguiSystem()->m_systemInitState == ImguiSystemInitStage_e::IM_PENDING_INIT) if (!ImguiSystem()->IsInitialized())
return NULL; return NULL;
AUTO_LOCK(ImguiSystem()->m_inputEventQueueMutex); AUTO_LOCK(ImguiSystem()->m_inputEventQueueMutex);

View File

@ -6,6 +6,7 @@
#ifndef IMGUI_SYSTEM_H #ifndef IMGUI_SYSTEM_H
#define IMGUI_SYSTEM_H #define IMGUI_SYSTEM_H
#include "imgui/misc/imgui_snapshot.h" #include "imgui/misc/imgui_snapshot.h"
#include "imgui_surface.h"
class CImguiSystem class CImguiSystem
{ {
@ -15,6 +16,9 @@ public:
bool Init(); bool Init();
void Shutdown(); void Shutdown();
void AddSurface(CImguiSurface* const surface);
void RemoveSurface(CImguiSurface* const surface);
void SwapBuffers(); void SwapBuffers();
void SampleFrame(); void SampleFrame();
@ -24,34 +28,11 @@ public:
static LRESULT MessageHandler(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); static LRESULT MessageHandler(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
// inlines: // inlines:
inline bool IsInitialized() const inline bool IsInitialized() const { return m_initialized; };
{
return m_systemInitState >= ImguiSystemInitStage_e::IM_SYSTEM_INIT;
};
private: private:
enum class ImguiSystemInitStage_e
{
// When the system failed to initialize, the stage would be set to
// this.
IM_INIT_FAILURE = -1,
IM_PENDING_INIT,
IM_SYSTEM_INIT,
// State gets set to this when the first frame has been sampled.
IM_FRAME_SAMPLED,
// State gets set to this then buffers have been swapped for the first
// time.
IM_FRAME_SWAPPED,
// Rendered for the first time.
IM_FRAME_RENDERED
};
ImguiSystemInitStage_e m_systemInitState;
ImDrawDataSnapshot m_snapshotData; ImDrawDataSnapshot m_snapshotData;
CUtlVector<CImguiSurface* const> m_surfaceList;
// Mutex used during swapping and rendering, we draw the windows in the // Mutex used during swapping and rendering, we draw the windows in the
// main thread, and render it in the render thread. The only place this // main thread, and render it in the render thread. The only place this
@ -63,6 +44,9 @@ private:
// is ran in thread separate from the main thread, therefore it needs a // is ran in thread separate from the main thread, therefore it needs a
// lock to control access as main calls SampleFrame(). // lock to control access as main calls SampleFrame().
mutable CThreadMutex m_inputEventQueueMutex; mutable CThreadMutex m_inputEventQueueMutex;
bool m_initialized;
bool m_hasNewFrame;
}; };
CImguiSystem* ImguiSystem(); CImguiSystem* ImguiSystem();

View File

@ -139,7 +139,10 @@ void* __fastcall DispatchDrawCall(int64_t a1, uint64_t a2, int a3, int a4, int64
//--------------------------------------------------------------------------------- //---------------------------------------------------------------------------------
ssize_t SpinPresent(void) ssize_t SpinPresent(void)
{ {
ImguiSystem()->RenderFrame(); CImguiSystem* const imguiSystem = ImguiSystem();
if (imguiSystem->IsInitialized())
imguiSystem->RenderFrame();
const ssize_t val = v_SpinPresent(); const ssize_t val = v_SpinPresent();
return val; return val;
@ -147,8 +150,13 @@ ssize_t SpinPresent(void)
void* CMaterialSystem::SwapBuffers(CMaterialSystem* pMatSys) void* CMaterialSystem::SwapBuffers(CMaterialSystem* pMatSys)
{ {
ImguiSystem()->SampleFrame(); CImguiSystem* const imguiSystem = ImguiSystem();
ImguiSystem()->SwapBuffers();
if (imguiSystem->IsInitialized())
{
imguiSystem->SampleFrame();
imguiSystem->SwapBuffers();
}
return CMaterialSystem__SwapBuffers(pMatSys); return CMaterialSystem__SwapBuffers(pMatSys);
} }

View File

@ -3,6 +3,7 @@
//------------------------------ //------------------------------
#define STB_IMAGE_IMPLEMENTATION #define STB_IMAGE_IMPLEMENTATION
#include "tier0/threadtools.h" #include "tier0/threadtools.h"
#include "tier0/commandline.h"
#include "tier1/cvar.h" #include "tier1/cvar.h"
#include "windows/id3dx.h" #include "windows/id3dx.h"
#include "windows/input.h" #include "windows/input.h"
@ -350,9 +351,17 @@ void DirectX_Init()
Error(eDLL_T::COMMON, 0xBAD0C0DE, "Failed to detour process: error code = %08x\n", hr); Error(eDLL_T::COMMON, 0xBAD0C0DE, "Failed to detour process: error code = %08x\n", hr);
} }
if (!ImguiSystem()->Init()) if (!CommandLine()->CheckParm("-noimgui"))
{
if (ImguiSystem()->Init())
{
ImguiSystem()->AddSurface(&g_Console);
ImguiSystem()->AddSurface(&g_Browser);
}
else
Error(eDLL_T::COMMON, 0, "ImguiSystem()->Init() failed!\n"); Error(eDLL_T::COMMON, 0, "ImguiSystem()->Init() failed!\n");
} }
}
void DirectX_Shutdown() void DirectX_Shutdown()
{ {
@ -371,7 +380,13 @@ void DirectX_Shutdown()
// Commit the transaction // Commit the transaction
DetourTransactionCommit(); DetourTransactionCommit();
if (ImguiSystem()->IsInitialized())
{
ImguiSystem()->Shutdown(); ImguiSystem()->Shutdown();
ImguiSystem()->RemoveSurface(&g_Browser);
ImguiSystem()->RemoveSurface(&g_Console);
}
} }
void VDXGI::GetAdr(void) const void VDXGI::GetAdr(void) const