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 "windows/id3dx.h"
#include "IBrowser.h"
#include "IConsole.h"
#include "imgui_system.h"
//-----------------------------------------------------------------------------
@ -18,7 +15,8 @@
//-----------------------------------------------------------------------------
CImguiSystem::CImguiSystem()
{
m_systemInitState = ImguiSystemInitStage_e::IM_PENDING_INIT;
m_initialized = false;
m_hasNewFrame = false;
}
//-----------------------------------------------------------------------------
@ -28,7 +26,7 @@ CImguiSystem::CImguiSystem()
bool CImguiSystem::Init()
{
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();
@ -55,12 +53,12 @@ bool CImguiSystem::Init()
!ImGui_ImplDX11_Init(D3D11Device(), D3D11DeviceContext()))
{
Assert(0);
m_systemInitState = ImguiSystemInitStage_e::IM_INIT_FAILURE;
return false;
}
m_systemInitState = ImguiSystemInitStage_e::IM_SYSTEM_INIT;
m_initialized = true;
m_hasNewFrame = false;
return true;
}
@ -70,23 +68,62 @@ bool CImguiSystem::Init()
void CImguiSystem::Shutdown()
{
Assert(ThreadInMainThread(), "CImguiSystem::Shutdown() should only be called from the main thread!");
Assert(m_systemInitState != ImguiSystemInitStage_e::IM_PENDING_INIT, "CImguiSystem::Shutdown() called recursively?");
// Nothing to shutdown.
if (m_systemInitState == ImguiSystemInitStage_e::IM_PENDING_INIT)
return;
Assert(IsInitialized(), "CImguiSystem::Shutdown() called recursively?");
AUTO_LOCK(m_snapshotBufferMutex);
AUTO_LOCK(m_inputEventQueueMutex);
m_systemInitState = ImguiSystemInitStage_e::IM_PENDING_INIT;
ImGui_ImplDX11_Shutdown();
ImGui_ImplWin32_Shutdown();
ImGui::DestroyContext();
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()
{
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;
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());
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;
m_hasNewFrame = true;
}
//-----------------------------------------------------------------------------
@ -146,16 +154,14 @@ void CImguiSystem::SampleFrame()
//-----------------------------------------------------------------------------
void CImguiSystem::RenderFrame()
{
if (m_systemInitState < ImguiSystemInitStage_e::IM_FRAME_SWAPPED)
Assert(IsInitialized());
AUTO_LOCK(m_snapshotBufferMutex);
if (!m_hasNewFrame)
return;
{
AUTO_LOCK(m_snapshotBufferMutex);
ImGui_ImplDX11_RenderDrawData(&m_snapshotData.DrawData);
}
if (m_systemInitState == ImguiSystemInitStage_e::IM_FRAME_SAMPLED)
m_systemInitState = ImguiSystemInitStage_e::IM_FRAME_RENDERED;
ImGui_ImplDX11_RenderDrawData(&m_snapshotData.DrawData);
m_hasNewFrame = false;
}
//-----------------------------------------------------------------------------
@ -163,7 +169,7 @@ void CImguiSystem::RenderFrame()
//-----------------------------------------------------------------------------
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;
AUTO_LOCK(ImguiSystem()->m_inputEventQueueMutex);

View File

@ -6,6 +6,7 @@
#ifndef IMGUI_SYSTEM_H
#define IMGUI_SYSTEM_H
#include "imgui/misc/imgui_snapshot.h"
#include "imgui_surface.h"
class CImguiSystem
{
@ -15,6 +16,9 @@ public:
bool Init();
void Shutdown();
void AddSurface(CImguiSurface* const surface);
void RemoveSurface(CImguiSurface* const surface);
void SwapBuffers();
void SampleFrame();
@ -24,34 +28,11 @@ public:
static LRESULT MessageHandler(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
// inlines:
inline bool IsInitialized() const
{
return m_systemInitState >= ImguiSystemInitStage_e::IM_SYSTEM_INIT;
};
inline bool IsInitialized() const { return m_initialized; };
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;
CUtlVector<CImguiSurface* const> m_surfaceList;
// 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
@ -63,6 +44,9 @@ private:
// is ran in thread separate from the main thread, therefore it needs a
// lock to control access as main calls SampleFrame().
mutable CThreadMutex m_inputEventQueueMutex;
bool m_initialized;
bool m_hasNewFrame;
};
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)
{
ImguiSystem()->RenderFrame();
CImguiSystem* const imguiSystem = ImguiSystem();
if (imguiSystem->IsInitialized())
imguiSystem->RenderFrame();
const ssize_t val = v_SpinPresent();
return val;
@ -147,8 +150,13 @@ ssize_t SpinPresent(void)
void* CMaterialSystem::SwapBuffers(CMaterialSystem* pMatSys)
{
ImguiSystem()->SampleFrame();
ImguiSystem()->SwapBuffers();
CImguiSystem* const imguiSystem = ImguiSystem();
if (imguiSystem->IsInitialized())
{
imguiSystem->SampleFrame();
imguiSystem->SwapBuffers();
}
return CMaterialSystem__SwapBuffers(pMatSys);
}

View File

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