Skip to content

Instantly share code, notes, and snippets.

@fredemmott
Last active November 23, 2024 15:44
Show Gist options
  • Select an option

  • Save fredemmott/edddea2b87ed6dd3e9fc738b8da3f209 to your computer and use it in GitHub Desktop.

Select an option

Save fredemmott/edddea2b87ed6dd3e9fc738b8da3f209 to your computer and use it in GitHub Desktop.
HTCCSettingsApp skeleton - example ImGUI with Win32/D3D11
// Copyright 2024 Fred Emmott <fred@fredemmott.com>
// SPDX-License-Identifier: MIT
#include <Windows.h>
#include <d3d11.h>
#include <dxgi1_3.h>
#include <imgui.h>
#include <imgui_impl_dx11.h>
#include <imgui_impl_win32.h>
#include <wil/com.h>
#include <wil/resource.h>
#include <winuser.h>
#include <chrono>
#include <optional>
extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(
HWND hWnd,
UINT msg,
WPARAM wParam,
LPARAM lParam);
class HTCCSettingsApp {
public:
HTCCSettingsApp() = delete;
explicit HTCCSettingsApp(const HINSTANCE instance) {
const WNDCLASSW wc {
.lpfnWndProc = &WindowProc,
.hInstance = instance,
.lpszClassName = L"HTCC Settings",
};
const auto classAtom = RegisterClass(&wc);
mHwnd.reset(CreateWindowExW(
WS_EX_APPWINDOW | WS_EX_CLIENTEDGE,
wc.lpszClassName,
L"HTCC Settings",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
nullptr,
nullptr,
instance,
nullptr));
RECT windowRect {};
GetClientRect(mHwnd.get(), &windowRect);
UINT d3dFlags = D3D11_CREATE_DEVICE_SINGLETHREADED;
UINT dxgiFlags = 0;
d3dFlags |= D3D11_CREATE_DEVICE_DEBUG;
dxgiFlags |= DXGI_CREATE_FACTORY_DEBUG;
THROW_IF_FAILED(D3D11CreateDevice(
nullptr,
D3D_DRIVER_TYPE_HARDWARE,
nullptr,
d3dFlags,
nullptr,
0,
D3D11_SDK_VERSION,
mD3DDevice.put(),
nullptr,
mD3DContext.put()));
wil::com_ptr<IDXGIFactory3> dxgiFactory;
THROW_IF_FAILED(
CreateDXGIFactory2(dxgiFlags, IID_PPV_ARGS(dxgiFactory.put())));
DXGI_SWAP_CHAIN_DESC1 swapChainDesc {
.Width = static_cast<UINT>(windowRect.right - windowRect.left),
.Height = static_cast<UINT>(windowRect.bottom - windowRect.top),
.Format = DXGI_FORMAT_R8G8B8A8_UNORM,
.SampleDesc = {1, 0},
.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT,
.BufferCount = 2,
.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD,
.AlphaMode = DXGI_ALPHA_MODE_IGNORE,
};
THROW_IF_FAILED(dxgiFactory->CreateSwapChainForHwnd(
mD3DDevice.get(),
mHwnd.get(),
&swapChainDesc,
nullptr,
nullptr,
mSwapChain.put()));
wil::com_ptr<ID3D11Texture2D> backBuffer;
THROW_IF_FAILED(mSwapChain->GetBuffer(0, IID_PPV_ARGS(backBuffer.put())));
THROW_IF_FAILED(mD3DDevice->CreateRenderTargetView(
backBuffer.get(), nullptr, mRenderTargetView.put()));
IMGUI_CHECKVERSION();
ImGui::CreateContext();
auto& io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
ImGui_ImplWin32_Init(mHwnd.get());
ImGui_ImplDX11_Init(mD3DDevice.get(), mD3DContext.get());
}
~HTCCSettingsApp() {
ImGui_ImplDX11_Shutdown();
ImGui_ImplWin32_Shutdown();
ImGui::DestroyContext();
}
[[nodiscard]] HWND GetHWND() const noexcept {
return mHwnd.get();
}
[[nodiscard]] int Run() noexcept {
constexpr auto frameRate = 60;
constexpr auto frameInterval
= std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::seconds(1))
/ frameRate;
while (!mExitCode) {
const auto start = std::chrono::steady_clock::now();
MSG msg {};
while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
if (msg.message == WM_QUIT) {
return mExitCode.value_or(0);
}
}
const auto rawRTV = mRenderTargetView.get();
FLOAT clearColor[4] {0.0f, 0.0f, 0.0f, 1.0f};
mD3DContext->ClearRenderTargetView(rawRTV, clearColor);
mD3DContext->OMSetRenderTargets(1, &rawRTV, nullptr);
ImGui_ImplDX11_NewFrame();
ImGui_ImplWin32_NewFrame();
ImGui::NewFrame();
this->FrameTick();
ImGui::Render();
ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());
mSwapChain->Present(1, 0);
const auto end = std::chrono::steady_clock::now();
const auto frameDuration = end - start;
const auto wait = frameInterval - frameDuration;
if (wait > std::chrono::microseconds::zero()) {
MsgWaitForMultipleObjects(
0,
nullptr,
FALSE,
std::chrono::duration_cast<std::chrono::milliseconds>(wait).count(),
QS_ALLEVENTS);
}
}
return *mExitCode;
}
private:
wil::unique_hwnd mHwnd;
wil::com_ptr<IDXGISwapChain1> mSwapChain;
wil::com_ptr<ID3D11Device> mD3DDevice;
wil::com_ptr<ID3D11DeviceContext> mD3DContext;
wil::com_ptr<ID3D11RenderTargetView> mRenderTargetView;
std::optional<int> mExitCode;
static LRESULT
WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) noexcept {
if (ImGui_ImplWin32_WndProcHandler(hwnd, uMsg, wParam, lParam)) {
return true;
}
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
}
void FrameTick() noexcept {
ImGui::ShowDemoWindow();
}
};
int WINAPI wWinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPWSTR lpCmdLine,
int nCmdShow) {
CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
HTCCSettingsApp app(hInstance);
ShowWindow(app.GetHWND(), nCmdShow);
return app.Run();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment