如何在带有DX11绘图界面的软件上画imgui界面

前言

处于某些研究目的,我们经常需要在dx11绘制的界面上绘制我们自己的操作菜单,以方便进行一些可视化操作;这里面imgui库因为其优越的可用性,健壮性和美观性,得到了很多人的青睐。那么我们应该如何在一个带有dx的软件界面上利用imgui绘制我们自己的界面呢?下面的代码就是为了解决这个问题的(采用dx11版本举例)。

代码样例

#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <memory>
#include <thread>
#include <windows.h>

#include <d3d11.h>

#include "Imgui/imgui.h"
#include "Imgui/imgui_impl_dx11.h"
#include "Imgui/imgui_impl_win32.h"

#ifdef _DEBUG
#define LOG(_text, ...) printf(_text "\n", __VA_ARGS__)
#else
#define LOG(_text, ...)
#endif

namespace {
/*
 * 0 ProcessAttach NoCall
 * 1 ProcessAttach Called
 */
LONG volatile g_iDllMainProcessAttachFlag = 0;
LONG volatile g_iDX11NeedInit = TRUE;

BOOL g_bInitSuccess = FALSE;

HWND g_GameHwnd = NULL;

IDXGISwapChain *g_pHookedSwapChain = nullptr;
IDXGISwapChain *g_pSwapChain = nullptr;

ID3D11Device *g_pd3dDevice = nullptr;
ID3D11DeviceContext *g_pd3dDeviceContext = nullptr;
ID3D11RenderTargetView *g_mainRenderTargetView = nullptr;
}  // namespace

void CleanupRenderTarget() {
  if (g_mainRenderTargetView) {
    g_mainRenderTargetView->Release();
    g_mainRenderTargetView = nullptr;
  }
}

void CleanupDeviceD3D() {
  CleanupRenderTarget();
  if (g_pHookedSwapChain) {
    g_pHookedSwapChain->Release();
    g_pHookedSwapChain = nullptr;
  }
  if (g_pSwapChain) {
    g_pSwapChain->Release();
    g_pSwapChain = nullptr;
  }
  if (g_pd3dDeviceContext) {
    g_pd3dDeviceContext->Release();
    g_pd3dDeviceContext = nullptr;
  }
  if (g_pd3dDevice) {
    g_pd3dDevice->Release();
    g_pd3dDevice = nullptr;
  }
}

void CreateRenderTarget() {
  ID3D11Texture2D *pBackBuffer = nullptr;
  g_pSwapChain->GetBuffer(0, IID_PPV_ARGS(&pBackBuffer));
  g_pd3dDevice->CreateRenderTargetView(pBackBuffer, nullptr,
                                       &g_mainRenderTargetView);
  pBackBuffer->Release();
}

LRESULT CreateDeviceD3D(HWND hWnd) {
  // Setup swap chain
  DXGI_SWAP_CHAIN_DESC sd;
  ZeroMemory(&sd, sizeof(sd));
  sd.BufferCount = 2;
  sd.BufferDesc.Width = 0;
  sd.BufferDesc.Height = 0;
  sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
  sd.BufferDesc.RefreshRate.Numerator = 60;
  sd.BufferDesc.RefreshRate.Denominator = 1;
  sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
  sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
  sd.OutputWindow = hWnd;
  sd.SampleDesc.Count = 1;
  sd.SampleDesc.Quality = 0;
  sd.Windowed = TRUE;
  sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;

  UINT createDeviceFlags = 0;
  // createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
  D3D_FEATURE_LEVEL featureLevel;

  ID3D11Device *pd3dDevice = nullptr;
  ID3D11DeviceContext *pd3dDeviceContext = nullptr;

  const D3D_FEATURE_LEVEL featureLevelArray[2] = {
      D3D_FEATURE_LEVEL_11_0,
      D3D_FEATURE_LEVEL_10_0,
  };
  HRESULT res = D3D11CreateDeviceAndSwapChain(
      nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, createDeviceFlags,
      featureLevelArray, 2, D3D11_SDK_VERSION, &sd, &g_pHookedSwapChain,
      &pd3dDevice, &featureLevel, &pd3dDeviceContext);
  if (res == DXGI_ERROR_UNSUPPORTED)  // Try high-performance WARP software
                                      // driver if hardware is not available.
    res = D3D11CreateDeviceAndSwapChain(
        nullptr, D3D_DRIVER_TYPE_WARP, nullptr, createDeviceFlags,
        featureLevelArray, 2, D3D11_SDK_VERSION, &sd, &g_pHookedSwapChain,
        &pd3dDevice, &featureLevel, &pd3dDeviceContext);

  // We just need SwapChain, to get its virtual table and hook It!!
  if (pd3dDeviceContext) {
    pd3dDeviceContext->Release();
    pd3dDeviceContext = nullptr;
  }
  if (pd3dDevice) {
    pd3dDevice->Release();
    pd3dDevice = nullptr;
  }

  return res;
}

// Forward declare message handler from imgui_impl_win32.cpp
typedef LRESULT(WINAPI *typedef_WndProc)(HWND hWnd, UINT msg, WPARAM wParam,
                                         LPARAM lParam);

typedef HRESULT(STDMETHODCALLTYPE *typedef_Present)(
    /* [in] */ IDXGISwapChain *This,
    /* [in] */ UINT SyncInterval,
    /* [in] */ UINT Flags);

typedef HRESULT(STDMETHODCALLTYPE *typedef_ResizeBuffers)(
    /* [in] */ IDXGISwapChain *This,
    /* [in] */ UINT BufferCount,
    /* [in] */ UINT Width,
    /* [in] */ UINT Height,
    /* [in] */ DXGI_FORMAT NewFormat,
    /* [in] */ UINT SwapChainFlags);

extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd,
                                                             UINT msg,
                                                             WPARAM wParam,
                                                             LPARAM lParam);

static typedef_WndProc Real_WndProc = nullptr;
static constexpr typedef_WndProc Mine_WndProc =
    [](HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) -> LRESULT {
  LOG("Mine_WndProc Hooked!!");

  if (ImGui_ImplWin32_WndProcHandler(hWnd, msg, wParam, lParam)) return S_FALSE;

  return Real_WndProc ? CallWindowProcW(Real_WndProc, hWnd, msg, wParam, lParam)
                      : S_OK;
};

static typedef_Present Real_Present = nullptr;
static constexpr typedef_Present Mine_Present =
    [](IDXGISwapChain *This, UINT SyncInterval, UINT Flags) -> HRESULT {
  LOG("Mine_Present Hooked!!");

  if (InterlockedCompareExchange(&g_iDX11NeedInit, 0, 1)) {
    g_pSwapChain = This;
    g_pSwapChain->GetDevice(__uuidof(g_pd3dDevice), (void **)&g_pd3dDevice);
    g_pd3dDevice->GetImmediateContext(&g_pd3dDeviceContext);

    CreateRenderTarget();
    ImGui_ImplWin32_Init(g_GameHwnd);
    ImGui_ImplDX11_Init(g_pd3dDevice, g_pd3dDeviceContext);
  }

  // Start the Dear ImGui frame
  ImGui_ImplDX11_NewFrame();
  ImGui_ImplWin32_NewFrame();
  ImGui::NewFrame();

  ImGui::Begin("AHA");
  ImGui::Text("Gugu!!");
  ImGui::End();

  ImGui::Render();
  g_pd3dDeviceContext->OMSetRenderTargets(1, &g_mainRenderTargetView, nullptr);
  ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());

  return Real_Present(This, SyncInterval, Flags);
};

static typedef_ResizeBuffers Real_ResizeBuffers = nullptr;
static constexpr typedef_ResizeBuffers Mine_ResizeBuffers =
    [](IDXGISwapChain *This, UINT BufferCount, UINT Width, UINT Height,
       DXGI_FORMAT NewFormat, UINT SwapChainFlags) -> HRESULT {
  LOG("Mine_ResizeBuffers Hooked!!");

  ImGui_ImplDX11_Shutdown();
  ImGui_ImplWin32_Shutdown();
  CleanupDeviceD3D();
  InterlockedCompareExchange(&g_iDX11NeedInit, 1, 0);

  return Real_ResizeBuffers(This, BufferCount, Width, Height, NewFormat,
                            SwapChainFlags);
};

BOOL SetupHook() {
  Real_WndProc = reinterpret_cast<decltype(Real_WndProc)>(SetWindowLongPtrW(
      g_GameHwnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(Mine_WndProc)));

  DWORD dwOldProt = PAGE_EXECUTE_READ;
  if (!VirtualProtect(*reinterpret_cast<uintptr_t **>(g_pHookedSwapChain), 1,
                      PAGE_EXECUTE_READWRITE, &dwOldProt)) {
    return FALSE;
  }

  Real_Present = static_cast<decltype(Real_Present)>(InterlockedExchangePointer(
      reinterpret_cast<volatile PVOID *>(
          *reinterpret_cast<uintptr_t **>(g_pHookedSwapChain) + 8),
      Mine_Present));
  Real_ResizeBuffers =
      static_cast<decltype(Real_ResizeBuffers)>(InterlockedExchangePointer(
          reinterpret_cast<volatile PVOID *>(
              *reinterpret_cast<uintptr_t **>(g_pHookedSwapChain) + 13),
          Mine_ResizeBuffers));
  VirtualProtect(*reinterpret_cast<uintptr_t **>(g_pHookedSwapChain), 1,
                 dwOldProt, &dwOldProt);

  return TRUE;
}

void RemoveHook() {
  DWORD dwOldProt = PAGE_EXECUTE_READ;

  if (!VirtualProtect(*reinterpret_cast<uintptr_t **>(g_pHookedSwapChain), 1,
                      PAGE_EXECUTE_READWRITE, &dwOldProt)) {
    return;
  }

  InterlockedCompareExchangePointer(
      reinterpret_cast<volatile PVOID *>(
          *reinterpret_cast<uintptr_t **>(g_pHookedSwapChain) + 8),
      Real_Present, Mine_Present);
  InterlockedCompareExchangePointer(
      reinterpret_cast<volatile PVOID *>(
          *reinterpret_cast<uintptr_t **>(g_pHookedSwapChain) + 13),
      Real_ResizeBuffers, Mine_ResizeBuffers);
  VirtualProtect(*reinterpret_cast<uintptr_t **>(g_pHookedSwapChain), 1,
                 dwOldProt, &dwOldProt);

  if (Real_WndProc) {
    SetWindowLongPtrW(g_GameHwnd, GWLP_WNDPROC,
                      reinterpret_cast<LONG_PTR>(Real_WndProc));
  }
}

void LibraryHandler_ProcAttach(HANDLE hModule) {
  LOG("Attach Begin!!");

  // 这里要修改为你需要绘制的父窗口的类名,可通过spy++获取
  g_GameHwnd = FindWindowW(L"ImGui Example", NULL);

  LRESULT res = CreateDeviceD3D(g_GameHwnd);
  if (res != S_OK) {
    LOG("CreateDeviceD3D Failed, res=0x%X", static_cast<unsigned int>(res));
    CleanupDeviceD3D();
    return;
  }

  // Setup Dear ImGui context
  IMGUI_CHECKVERSION();
  ImGui::CreateContext();

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

  // Setup Platform/Renderer Backends
  if (!SetupHook()) {
    LOG("Setup Hook Failed");
  }

  LOG("Attach Init Success");
  g_bInitSuccess = TRUE;
}

void LibraryHandler_ProcDetach(HANDLE hModule) {
  LOG("Detach Begin!!");

  if (!g_bInitSuccess) {
    return;
  }

  RemoveHook();

  ImGui_ImplDX11_Shutdown();
  ImGui_ImplWin32_Shutdown();
  ImGui::DestroyContext();

  CleanupDeviceD3D();
}

BOOL WINAPI DllMain(HANDLE hModule, DWORD dwReason, LPVOID lpvReserved) {
  static FILE *g_stream = nullptr;

  switch (dwReason) {
    case DLL_PROCESS_ATTACH: {
      if (0 == InterlockedCompareExchange(&g_iDllMainProcessAttachFlag, 1, 0)) {
#ifdef _DEBUG
        AllocConsole();
        freopen_s(&g_stream, "CONOUT$", "w+", stdout);
#endif
        auto threadObj = std::thread(
            [hModule]() -> void { LibraryHandler_ProcAttach(hModule); });
        threadObj.detach();
      }
      break;
    }
    case DLL_PROCESS_DETACH: {
      LibraryHandler_ProcDetach(hModule);
#ifdef _DEBUG
      fflush(stdout);
      fclose(g_stream);
#endif
      break;
    }
    case DLL_THREAD_ATTACH:
      break;
    case DLL_THREAD_DETACH:
      break;
  }

  return TRUE;
}
posted @ 2024-07-20 17:06  倚剑问天  阅读(109)  评论(0编辑  收藏  举报